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.
Files changed (292) hide show
  1. package/CHANGELOG.md +597 -0
  2. package/README.md +43 -644
  3. package/framework/appLayout/default/chui_app_layout.js +602 -496
  4. package/framework/appLayout/default/global_style.css +288 -97
  5. package/framework/appLayout/default/main_theme_style.css +188 -101
  6. package/framework/components/chui_accordion/accordion.js +38 -35
  7. package/framework/components/chui_accordion/styles.css +13 -12
  8. package/framework/components/chui_action_sheet/action_sheet.js +93 -64
  9. package/framework/components/chui_action_sheet/styles.css +12 -5
  10. package/framework/components/chui_actions_bar/actions_bar.js +338 -0
  11. package/framework/components/chui_actions_bar/styles.css +139 -0
  12. package/framework/components/chui_affix/affix.js +16 -11
  13. package/framework/components/chui_affix/styles.css +5 -0
  14. package/framework/components/chui_alert/alert.js +34 -19
  15. package/framework/components/chui_alert/styles.css +7 -0
  16. package/framework/components/chui_avatar/avatar.js +36 -35
  17. package/framework/components/chui_avatar/styles.css +1 -1
  18. package/framework/components/chui_avatar_group/avatar_group.js +25 -23
  19. package/framework/components/chui_avatar_group/styles.css +1 -1
  20. package/framework/components/chui_backtop/backtop.js +60 -21
  21. package/framework/components/chui_backtop/styles.css +21 -0
  22. package/framework/components/chui_badge/badge.js +30 -28
  23. package/framework/components/chui_badge/styles.css +6 -6
  24. package/framework/components/chui_banner/banner.js +222 -0
  25. package/framework/components/chui_banner/styles.css +170 -0
  26. package/framework/components/chui_breadcrumb/breadcrumb.js +15 -13
  27. package/framework/components/chui_breadcrumb/styles.css +1 -1
  28. package/framework/components/chui_button/button.js +52 -36
  29. package/framework/components/chui_button/styles.css +7 -7
  30. package/framework/components/chui_calendar/calendar.js +18 -16
  31. package/framework/components/chui_card/card.js +52 -32
  32. package/framework/components/chui_card_scroll/card_scroll.js +169 -0
  33. package/framework/components/chui_card_scroll/styles.css +86 -0
  34. package/framework/components/chui_chat/chat.js +140 -79
  35. package/framework/components/chui_chat/styles.css +18 -7
  36. package/framework/components/chui_clipboard_history/clipboard_history.js +53 -39
  37. package/framework/components/chui_clipboard_history/styles.css +13 -9
  38. package/framework/components/chui_code/code.js +10 -10
  39. package/framework/components/chui_code/styles.css +4 -4
  40. package/framework/components/chui_code_editor/code_editor.js +73 -57
  41. package/framework/components/chui_code_editor/styles.css +6 -5
  42. package/framework/components/chui_command_palette/command_palette.js +87 -64
  43. package/framework/components/chui_command_palette/styles.css +5 -5
  44. package/framework/components/chui_console/console.js +13 -13
  45. package/framework/components/chui_console/styles.css +6 -3
  46. package/framework/components/chui_content_block/content_block.js +35 -21
  47. package/framework/components/chui_content_block/styles.css +2 -2
  48. package/framework/components/chui_context_menu/context_menu.js +171 -58
  49. package/framework/components/chui_context_menu/styles.css +82 -8
  50. package/framework/components/chui_custom_element/custom_element.js +25 -18
  51. package/framework/components/chui_data_grid/data_grid.js +633 -471
  52. package/framework/components/chui_data_grid/styles.css +24 -22
  53. package/framework/components/chui_descriptions/descriptions.js +34 -17
  54. package/framework/components/chui_descriptions/styles.css +1 -1
  55. package/framework/components/chui_details/details.js +42 -36
  56. package/framework/components/chui_details/styles.css +12 -11
  57. package/framework/components/chui_diff_viewer/diff_viewer.js +172 -147
  58. package/framework/components/chui_diff_viewer/styles.css +10 -10
  59. package/framework/components/chui_divider/divider.js +19 -17
  60. package/framework/components/chui_divider/styles.css +2 -2
  61. package/framework/components/chui_drawer/drawer.js +63 -38
  62. package/framework/components/chui_drawer/styles.css +18 -4
  63. package/framework/components/chui_drop_zone/drop_zone.js +37 -27
  64. package/framework/components/chui_drop_zone/styles.css +10 -6
  65. package/framework/components/chui_empty_state/empty_state.js +20 -15
  66. package/framework/components/chui_fieldset/fieldset.js +40 -23
  67. package/framework/components/chui_fieldset/styles.css +6 -6
  68. package/framework/components/chui_file_explorer/file_explorer.js +260 -189
  69. package/framework/components/chui_file_explorer/styles.css +8 -7
  70. package/framework/components/chui_form/form.js +40 -28
  71. package/framework/components/chui_form/styles.css +1 -1
  72. package/framework/components/chui_gantt/gantt.js +102 -78
  73. package/framework/components/chui_gantt/styles.css +5 -5
  74. package/framework/components/chui_graphs/graphs.js +376 -239
  75. package/framework/components/chui_graphs/styles.css +27 -6
  76. package/framework/components/chui_hero/hero.js +98 -37
  77. package/framework/components/chui_hero/styles.css +176 -7
  78. package/framework/components/chui_html_block/html_block.js +8 -8
  79. package/framework/components/chui_html_block/styles.css +3 -3
  80. package/framework/components/chui_hx/hx.js +3 -3
  81. package/framework/components/chui_hx/styles.css +13 -6
  82. package/framework/components/chui_icons/icons.js +2115 -2115
  83. package/framework/components/chui_icons/styles.css +4 -3
  84. package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
  85. package/framework/components/chui_inputs/chui_check_box/styles.css +30 -21
  86. package/framework/components/chui_inputs/chui_chips/chips.js +141 -70
  87. package/framework/components/chui_inputs/chui_chips/styles.css +32 -7
  88. package/framework/components/chui_inputs/chui_color_picker/color_picker.js +55 -38
  89. package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
  90. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +127 -109
  91. package/framework/components/chui_inputs/chui_combo_box/styles.css +23 -21
  92. package/framework/components/chui_inputs/chui_date/date.js +161 -127
  93. package/framework/components/chui_inputs/chui_date/styles.css +20 -19
  94. package/framework/components/chui_inputs/chui_date_range/date_range.js +61 -50
  95. package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
  96. package/framework/components/chui_inputs/chui_datetime/datetime.js +47 -39
  97. package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
  98. package/framework/components/chui_inputs/chui_email/email.js +48 -36
  99. package/framework/components/chui_inputs/chui_email/styles.css +11 -11
  100. package/framework/components/chui_inputs/chui_file/file.js +71 -44
  101. package/framework/components/chui_inputs/chui_file/styles.css +23 -19
  102. package/framework/components/chui_inputs/chui_hotkey/hotkey.js +101 -76
  103. package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
  104. package/framework/components/chui_inputs/chui_masked/masked.js +83 -72
  105. package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
  106. package/framework/components/chui_inputs/chui_mention/mention.js +87 -40
  107. package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
  108. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +242 -212
  109. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +23 -15
  110. package/framework/components/chui_inputs/chui_number/number.js +84 -70
  111. package/framework/components/chui_inputs/chui_number/styles.css +13 -13
  112. package/framework/components/chui_inputs/chui_otp/otp.js +71 -62
  113. package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
  114. package/framework/components/chui_inputs/chui_password/password.js +72 -56
  115. package/framework/components/chui_inputs/chui_password/styles.css +15 -15
  116. package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
  117. package/framework/components/chui_inputs/chui_radio_button/styles.css +22 -22
  118. package/framework/components/chui_inputs/chui_rating/rating.js +54 -50
  119. package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
  120. package/framework/components/chui_inputs/chui_select_box/select_box.js +132 -108
  121. package/framework/components/chui_inputs/chui_select_box/styles.css +21 -21
  122. package/framework/components/chui_inputs/chui_slider/slider.js +41 -38
  123. package/framework/components/chui_inputs/chui_slider/styles.css +7 -5
  124. package/framework/components/chui_inputs/chui_text/styles.css +14 -14
  125. package/framework/components/chui_inputs/chui_text/text.js +69 -52
  126. package/framework/components/chui_inputs/chui_text_area/styles.css +14 -14
  127. package/framework/components/chui_inputs/chui_text_area/text_area.js +59 -36
  128. package/framework/components/chui_inputs/chui_time/styles.css +18 -20
  129. package/framework/components/chui_inputs/chui_time/time.js +71 -49
  130. package/framework/components/chui_inputs/chui_toggle/styles.css +13 -6
  131. package/framework/components/chui_inputs/chui_toggle/toggle.js +27 -27
  132. package/framework/components/chui_json_editor/json_editor.js +137 -127
  133. package/framework/components/chui_json_editor/styles.css +5 -5
  134. package/framework/components/chui_json_viewer/json_viewer.js +133 -114
  135. package/framework/components/chui_json_viewer/styles.css +9 -8
  136. package/framework/components/chui_kanban/kanban.js +78 -60
  137. package/framework/components/chui_kanban/styles.css +8 -4
  138. package/framework/components/chui_kbd/kbd.js +12 -10
  139. package/framework/components/chui_kbd/styles.css +1 -1
  140. package/framework/components/chui_label/label.js +31 -29
  141. package/framework/components/chui_label/styles.css +3 -3
  142. package/framework/components/chui_link/link.js +36 -29
  143. package/framework/components/chui_link/styles.css +11 -4
  144. package/framework/components/chui_list/list.js +42 -38
  145. package/framework/components/chui_list/styles.css +4 -3
  146. package/framework/components/chui_markdown_editor/markdown_editor.js +125 -109
  147. package/framework/components/chui_markdown_editor/styles.css +5 -5
  148. package/framework/components/chui_media/audio.js +457 -364
  149. package/framework/components/chui_media/audio_styles.css +74 -37
  150. package/framework/components/chui_media/image.js +99 -64
  151. package/framework/components/chui_media/image_compare.js +86 -55
  152. package/framework/components/chui_media/image_compare_styles.css +5 -5
  153. package/framework/components/chui_media/image_styles.css +4 -4
  154. package/framework/components/chui_media/video.js +264 -171
  155. package/framework/components/chui_media/video_styles.css +57 -24
  156. package/framework/components/chui_menu_bar/menu_bar.js +67 -65
  157. package/framework/components/chui_menu_bar/styles.css +14 -12
  158. package/framework/components/chui_message_bar/message_bar.js +551 -0
  159. package/framework/components/chui_message_bar/styles.css +248 -0
  160. package/framework/components/chui_modal/modal.js +81 -56
  161. package/framework/components/chui_modal/styles.css +3 -2
  162. package/framework/components/chui_notification/notification.js +107 -69
  163. package/framework/components/chui_notification/notification_download.js +28 -77
  164. package/framework/components/chui_notification/notification_download_card.js +115 -0
  165. package/framework/components/chui_notification/notification_download_progress.css +5 -4
  166. package/framework/components/chui_notification/notification_download_progress.js +73 -105
  167. package/framework/components/chui_notification/notification_update.js +44 -37
  168. package/framework/components/chui_notification/status_plate.css +3 -3
  169. package/framework/components/chui_notification/status_plate.js +2 -2
  170. package/framework/components/chui_notification/styles.css +6 -1
  171. package/framework/components/chui_notification/styles_download.css +2 -2
  172. package/framework/components/chui_notification/styles_update.css +3 -3
  173. package/framework/components/chui_org_chart/org_chart.js +101 -62
  174. package/framework/components/chui_org_chart/styles.css +2 -2
  175. package/framework/components/chui_pagination/pagination.js +65 -30
  176. package/framework/components/chui_pagination/styles.css +1 -1
  177. package/framework/components/chui_paragraph/paragraph.js +2 -2
  178. package/framework/components/chui_paragraph/styles.css +3 -3
  179. package/framework/components/chui_popover/popover.js +77 -50
  180. package/framework/components/chui_popover/styles.css +1 -1
  181. package/framework/components/chui_popups/popups.js +210 -181
  182. package/framework/components/chui_popups/styles.css +7 -7
  183. package/framework/components/chui_print_button/print_button.js +48 -33
  184. package/framework/components/chui_progress_bar/progress_bar.js +49 -19
  185. package/framework/components/chui_progress_bar/styles.css +6 -6
  186. package/framework/components/chui_progress_ring/progress_ring.js +32 -30
  187. package/framework/components/chui_progress_ring/styles.css +1 -1
  188. package/framework/components/chui_qrcode/qrcode.js +85 -41
  189. package/framework/components/chui_query_builder/query_builder.js +263 -170
  190. package/framework/components/chui_query_builder/styles.css +4 -4
  191. package/framework/components/chui_radio_group/radio_group.js +36 -26
  192. package/framework/components/chui_radio_group/styles.css +2 -2
  193. package/framework/components/chui_result/result.js +41 -31
  194. package/framework/components/chui_result/styles.css +3 -3
  195. package/framework/components/chui_scheduler/scheduler.js +126 -104
  196. package/framework/components/chui_scheduler/styles.css +11 -7
  197. package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
  198. package/framework/components/chui_screen_spinner/styles.css +49 -0
  199. package/framework/components/chui_segmented_control/segmented_control.js +90 -27
  200. package/framework/components/chui_segmented_control/styles.css +32 -6
  201. package/framework/components/chui_settings/settings.js +160 -87
  202. package/framework/components/chui_settings/styles.css +4 -4
  203. package/framework/components/chui_shortcut_help/shortcut_help.js +82 -61
  204. package/framework/components/chui_sidebar_nav/sidebar_nav.js +132 -53
  205. package/framework/components/chui_sidebar_nav/styles.css +9 -3
  206. package/framework/components/chui_skeleton/skeleton.js +29 -24
  207. package/framework/components/chui_slideshow/slideshow.js +122 -103
  208. package/framework/components/chui_slideshow/styles.css +22 -16
  209. package/framework/components/chui_spinner/spinner.js +17 -17
  210. package/framework/components/chui_spinner/styles.css +0 -1
  211. package/framework/components/chui_split_button/split_button.js +53 -30
  212. package/framework/components/chui_split_button/styles.css +22 -6
  213. package/framework/components/chui_splitter/splitter.js +46 -39
  214. package/framework/components/chui_splitter/styles.css +2 -2
  215. package/framework/components/chui_statistic/statistic.js +111 -84
  216. package/framework/components/chui_status_bar/status_bar.js +11 -9
  217. package/framework/components/chui_status_bar/styles.css +2 -2
  218. package/framework/components/chui_stepper/stepper.js +230 -54
  219. package/framework/components/chui_stepper/styles.css +79 -14
  220. package/framework/components/chui_sticky_bar/sticky_bar.js +53 -17
  221. package/framework/components/chui_sticky_bar/styles.css +11 -7
  222. package/framework/components/chui_tabs/chui_tabs.js +100 -46
  223. package/framework/components/chui_tabs/styles.css +43 -15
  224. package/framework/components/chui_text_editor/styles.css +24 -23
  225. package/framework/components/chui_text_editor/text_editor.js +103 -79
  226. package/framework/components/chui_text_editor/text_editor_panel.js +428 -367
  227. package/framework/components/chui_theme_toggle/theme_toggle.js +46 -25
  228. package/framework/components/chui_timeline/styles.css +3 -1
  229. package/framework/components/chui_timeline/timeline.js +14 -12
  230. package/framework/components/chui_toast_queue/styles.css +11 -11
  231. package/framework/components/chui_toast_queue/toast_queue.js +82 -73
  232. package/framework/components/chui_toolbar/styles.css +2 -2
  233. package/framework/components/chui_toolbar/toolbar.js +20 -13
  234. package/framework/components/chui_tooltip/styles.css +2 -2
  235. package/framework/components/chui_tooltip/tooltip.js +40 -27
  236. package/framework/components/chui_tour/styles.css +12 -7
  237. package/framework/components/chui_tour/tour.js +132 -98
  238. package/framework/components/chui_transfer/styles.css +8 -7
  239. package/framework/components/chui_transfer/transfer.js +143 -123
  240. package/framework/components/chui_tree_table/styles.css +4 -4
  241. package/framework/components/chui_tree_table/tree_table.js +88 -74
  242. package/framework/components/chui_tree_view/styles.css +14 -10
  243. package/framework/components/chui_tree_view/tree_view.js +298 -253
  244. package/framework/components/chui_user_bar/styles.css +145 -0
  245. package/framework/components/chui_user_bar/user_bar.js +402 -0
  246. package/framework/components/chui_user_list_bar/styles.css +173 -0
  247. package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
  248. package/framework/components/chui_watermark/watermark.js +41 -33
  249. package/framework/components/chui_webview/styles.css +2 -2
  250. package/framework/components/chui_webview/webview.js +64 -52
  251. package/framework/components/chui_write_bar/styles.css +172 -0
  252. package/framework/components/chui_write_bar/write_bar.js +320 -0
  253. package/framework/main_window.js +1234 -0
  254. package/framework/modules/chui_a11y/chui_a11y.js +153 -123
  255. package/framework/modules/chui_animations/animations.js +105 -43
  256. package/framework/modules/chui_animations/motion.js +120 -0
  257. package/framework/modules/chui_animations/styles.css +37 -4
  258. package/framework/modules/chui_bridge/preload.js +36 -25
  259. package/framework/modules/chui_functions.js +69 -63
  260. package/framework/modules/chui_highlight/chui_highlight.js +25 -22
  261. package/framework/modules/chui_highlight/styles.css +13 -10
  262. package/framework/modules/chui_i18n/chui_i18n.js +12 -10
  263. package/framework/modules/chui_i18n/locales/a11y.js +5 -5
  264. package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
  265. package/framework/modules/chui_i18n/locales/calendar.js +3 -3
  266. package/framework/modules/chui_i18n/locales/common.js +3 -3
  267. package/framework/modules/chui_i18n/locales/data.js +36 -26
  268. package/framework/modules/chui_i18n/locales/editors.js +40 -13
  269. package/framework/modules/chui_i18n/locales/feedback.js +65 -3
  270. package/framework/modules/chui_i18n/locales/forms.js +8 -8
  271. package/framework/modules/chui_i18n/locales/graphics.js +8 -8
  272. package/framework/modules/chui_i18n/locales/media.js +4 -4
  273. package/framework/modules/chui_i18n/locales/navigation.js +12 -8
  274. package/framework/modules/chui_i18n/locales/validate.js +3 -3
  275. package/framework/modules/chui_json/chui_json.js +11 -11
  276. package/framework/modules/chui_layout/chui_layout.js +12 -10
  277. package/framework/modules/chui_logger/chui_logger.js +47 -28
  278. package/framework/modules/chui_page/page.js +39 -33
  279. package/framework/modules/chui_palette/palette.js +61 -0
  280. package/framework/modules/chui_qr/chui_qr.js +179 -88
  281. package/framework/modules/chui_scrollbar/chui_scrollbar.js +79 -68
  282. package/framework/modules/chui_scrollbar/styles.css +4 -2
  283. package/framework/modules/chui_services/chui_channels.js +27 -13
  284. package/framework/modules/chui_services/chui_services.js +47 -39
  285. package/framework/modules/chui_services/desktop.js +108 -101
  286. package/framework/modules/chui_sortable/chui_sortable.js +33 -28
  287. package/framework/modules/chui_validate/chui_validate.js +10 -7
  288. package/framework/tray_bar/chui_menu_items.js +136 -46
  289. package/index.d.ts +5046 -0
  290. package/index.js +199 -1254
  291. package/package.json +81 -68
  292. package/framework/components/telegram_bot/chui_telegram_bot.js +0 -184
@@ -1,9 +1,9 @@
1
- const {Icon, Icons} = require("../chui_icons/icons");
2
- const {makeKeyboardButton} = require("../../modules/chui_a11y/chui_a11y");
3
- const {I18n} = require("../../modules/chui_i18n/chui_i18n");
1
+ const { Icon, Icons } = require("../chui_icons/icons");
2
+ const { makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
3
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
4
4
 
5
5
  /** Идентификаторы панелей дерева для aria-controls */
6
- let tree_panel_counter = 0;
6
+ let treePanelCounter = 0;
7
7
 
8
8
  /** Классы состояния узлов дерева */
9
9
  const SELECTED_CLASS = "tree_view_button_selected";
@@ -11,7 +11,7 @@ const DRAGGING_CLASS = "tree_view_button_dragging";
11
11
  const DROP_CLASSES = {
12
12
  before: "tree_view_button_drop_before",
13
13
  after: "tree_view_button_drop_after",
14
- inside: "tree_view_button_drop_inside"
14
+ inside: "tree_view_button_drop_inside",
15
15
  };
16
16
 
17
17
  /**
@@ -58,28 +58,28 @@ class TreeView {
58
58
  static SELECTION = {
59
59
  NONE: "none",
60
60
  SINGLE: "single",
61
- MULTIPLE: "multiple"
61
+ MULTIPLE: "multiple",
62
62
  };
63
63
  static DROP = {
64
64
  BEFORE: "before",
65
65
  AFTER: "after",
66
- INSIDE: "inside"
66
+ INSIDE: "inside",
67
67
  };
68
68
  // Узел → описание из фабрики: коллбэки и getSelected() отдают то же, что вернул TreeView.Button
69
69
  static #registry = new WeakMap();
70
70
  static #HIDDEN_CLASS = "tree_view_search_hidden";
71
71
 
72
- #chui_tree_view = document.createElement(`chui_tree_view`);
73
- #main_test = undefined;
74
- #selection_mode = TreeView.SELECTION.NONE;
72
+ #chuiTreeView = document.createElement(`chui_tree_view`);
73
+ #mainTest = undefined;
74
+ #selectionMode = TreeView.SELECTION.NONE;
75
75
  #selected = new Set();
76
76
  #anchor = undefined;
77
77
  #draggable = false;
78
- #move_on_drop = false;
79
- #on_select = undefined;
80
- #on_drop = undefined;
81
- #drag_source = undefined;
82
- #drop_target = undefined;
78
+ #moveOnDrop = false;
79
+ #onSelect = undefined;
80
+ #onDrop = undefined;
81
+ #dragSource = undefined;
82
+ #dropTarget = undefined;
83
83
  // Поиск: запрос, совпадения и скрытый статус для скринридера
84
84
  #query = "";
85
85
  #matches = new Set();
@@ -87,120 +87,130 @@ class TreeView {
87
87
  // Виртуализация: структурная модель (все узлы) + плоский список видимых строк
88
88
  #virtual = undefined;
89
89
  // Переменная высота строк: из данных (`rowHeight`-функция) или из DOM (`variableHeight`)
90
- #variable_height = false;
91
- #measure_dom = false;
90
+ #variableHeight = false;
91
+ #measureDom = false;
92
92
  #heights = new Map();
93
- #row_heights = [];
93
+ #rowHeights = [];
94
94
  #offsets = [0];
95
- #subtree_heights = new Map();
95
+ #subtreeHeights = new Map();
96
96
  #nodes = [];
97
- #node_index = new Map();
97
+ #nodeIndex = new Map();
98
98
  #roots = [];
99
99
  #rows = [];
100
- #child_rows = new Map();
101
- #root_rows = [];
102
- #row_sizes = new Map();
103
- #row_positions = new Map();
104
- #rows_dirty = true;
100
+ #childRows = new Map();
101
+ #rootRows = [];
102
+ #rowSizes = new Map();
103
+ #rowPositions = new Map();
104
+ #rowsDirty = true;
105
105
  // Счётчик изменений структуры: окно пересобирается, когда он разошёлся с отрисованным
106
106
  #stamp = 0;
107
- #win_stamp = -1;
108
- #win_rows = -1;
107
+ #winStamp = -1;
108
+ #winRows = -1;
109
109
  #win = undefined;
110
- #measured_row_height = undefined;
111
- #scroll_bound = false;
110
+ #measuredRowHeight = undefined;
111
+ #scrollBound = false;
112
112
  #raf = null;
113
113
  #destroyed = false;
114
114
 
115
- constructor(options = {
116
- width: String(),
117
- components: [],
118
- selection: String(),
119
- draggable: Boolean(),
120
- moveOnDrop: Boolean(),
121
- label: String(),
122
- onSelect: undefined,
123
- onDrop: undefined,
124
- deferRender: Boolean(),
125
- virtual: undefined,
126
- variableHeight: Boolean(),
127
- estimatedRowHeight: undefined,
128
- height: String()
129
- }) {
130
- require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_TreeView');
131
- let main_test = document.createElement(`chui_tree_view_main_test`);
132
- main_test.setAttribute("role", "tree");
133
- this.#main_test = main_test;
115
+ constructor(
116
+ options = {
117
+ width: String(),
118
+ components: [],
119
+ selection: String(),
120
+ draggable: Boolean(),
121
+ moveOnDrop: Boolean(),
122
+ label: String(),
123
+ onSelect: undefined,
124
+ onDrop: undefined,
125
+ deferRender: Boolean(),
126
+ virtual: undefined,
127
+ variableHeight: Boolean(),
128
+ estimatedRowHeight: undefined,
129
+ height: String(),
130
+ }
131
+ ) {
132
+ require("../../modules/chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_TreeView");
133
+ const treeNode = document.createElement(`chui_tree_view_main_test`);
134
+ treeNode.setAttribute("role", "tree");
135
+ this.#mainTest = treeNode;
134
136
  // Виртуализация включается явно: `virtual: true` или `{rowHeight, buffer}`
135
- this.#virtual = options.virtual === undefined || options.virtual === false
136
- ? undefined
137
- : (options.virtual === true ? {} : {...options.virtual});
137
+ this.#virtual =
138
+ options.virtual === undefined || options.virtual === false
139
+ ? undefined
140
+ : options.virtual === true
141
+ ? {}
142
+ : { ...options.virtual };
138
143
  // Переменная высота: функция `rowHeight(record, index)` — высоты из данных,
139
144
  // `variableHeight: true` — высоты измеряются по отрисованным строкам
140
145
  if (this.#virtual !== undefined) {
141
146
  if (options.variableHeight === true) this.#virtual.variableHeight = true;
142
- this.#variable_height = typeof this.#virtual.rowHeight === "function";
143
- this.#measure_dom = this.#virtual.variableHeight === true;
144
- if (this.#measure_dom && this.#virtual.estimatedRowHeight === undefined) this.#virtual.estimatedRowHeight = 32;
147
+ this.#variableHeight = typeof this.#virtual.rowHeight === "function";
148
+ this.#measureDom = this.#virtual.variableHeight === true;
149
+ if (this.#measureDom && this.#virtual.estimatedRowHeight === undefined)
150
+ this.#virtual.estimatedRowHeight = 32;
145
151
  }
146
- this.#selection_mode = options.selection === undefined ? TreeView.SELECTION.NONE : options.selection;
152
+ this.#selectionMode = options.selection === undefined ? TreeView.SELECTION.NONE : options.selection;
147
153
  this.#draggable = options.draggable === true;
148
- this.#move_on_drop = options.moveOnDrop === true;
149
- this.#on_select = options.onSelect;
150
- this.#on_drop = options.onDrop;
151
- if (options.label !== undefined) main_test.setAttribute("aria-label", options.label);
154
+ this.#moveOnDrop = options.moveOnDrop === true;
155
+ this.#onSelect = options.onSelect;
156
+ this.#onDrop = options.onDrop;
157
+ if (options.label !== undefined) treeNode.setAttribute("aria-label", options.label);
152
158
  // Отложенная отрисовка строк: узлы в DOM, но далёкие от окна не раскладываются
153
- if (options.deferRender === true) main_test.classList.add("tree_view_defer");
154
- if (this.#selection_mode !== TreeView.SELECTION.NONE) {
155
- main_test.setAttribute("aria-multiselectable", this.#selection_mode === TreeView.SELECTION.MULTIPLE ? "true" : "false");
156
- main_test.addEventListener("click", this.#onClick);
159
+ if (options.deferRender === true) treeNode.classList.add("tree_view_defer");
160
+ if (this.#selectionMode !== TreeView.SELECTION.NONE) {
161
+ treeNode.setAttribute(
162
+ "aria-multiselectable",
163
+ this.#selectionMode === TreeView.SELECTION.MULTIPLE ? "true" : "false"
164
+ );
165
+ treeNode.addEventListener("click", this.#onClick);
157
166
  // Space приходит и от makeKeyboardButton (кликом), поэтому перехватываем его
158
167
  // с модификатором раньше — в фазе перехвата
159
- main_test.addEventListener("keydown", this.#onSelectionKeyDown, true);
168
+ treeNode.addEventListener("keydown", this.#onSelectionKeyDown, true);
160
169
  }
161
170
  // Стрелки по дереву: ↑/↓ по видимым пунктам, → раскрыть, ← свернуть, Home/End — края
162
- main_test.addEventListener("keydown", this.#onNavigationKeyDown);
171
+ treeNode.addEventListener("keydown", this.#onNavigationKeyDown);
163
172
  if (this.#draggable) {
164
- main_test.addEventListener("dragstart", this.#onDragStart);
165
- main_test.addEventListener("dragover", this.#onDragOver);
166
- main_test.addEventListener("dragleave", this.#onDragLeave);
167
- main_test.addEventListener("drop", this.#onDropEvent);
168
- main_test.addEventListener("dragend", this.#onDragEnd);
173
+ treeNode.addEventListener("dragstart", this.#onDragStart);
174
+ treeNode.addEventListener("dragover", this.#onDragOver);
175
+ treeNode.addEventListener("dragleave", this.#onDragLeave);
176
+ treeNode.addEventListener("drop", this.#onDropEvent);
177
+ treeNode.addEventListener("dragend", this.#onDragEnd);
169
178
  }
170
179
  if (options.width !== undefined) {
171
- this.#chui_tree_view.style.width = options.width;
180
+ this.#chuiTreeView.style.width = options.width;
172
181
  } else {
173
- this.#chui_tree_view.style.width = 'max-content'
182
+ this.#chuiTreeView.style.width = "max-content";
174
183
  }
175
184
  // Панели действий приходят опцией: частичный объект опций без них — дерево без панелей
176
- const toolbar_buttons = Array.isArray(options.components) ? options.components : [];
177
- toolbar_buttons.forEach(buttons => {
178
- main_test.appendChild(buttons.button)
185
+ const toolbarButtons = Array.isArray(options.components) ? options.components : [];
186
+ toolbarButtons.forEach((buttons) => {
187
+ treeNode.appendChild(buttons.button);
179
188
  if (buttons.panel !== undefined) {
180
- main_test.appendChild(buttons.panel)
189
+ treeNode.appendChild(buttons.panel);
181
190
  }
182
- })
191
+ });
183
192
  // Структурная модель строится один раз по полному DOM: дальше окно собирается из неё,
184
193
  // поэтому отрисовка перестаёт зависеть от того, что сейчас лежит в документе
185
194
  this.#buildStructure();
186
195
  if (this.#virtual !== undefined) {
187
- this.#chui_tree_view.style.height = options.height === undefined ? "400px" : options.height;
196
+ this.#chuiTreeView.style.height = options.height === undefined ? "400px" : options.height;
188
197
  // Высота строки одна для вёрстки и для арифметики распорок (в переменном режиме её нет)
189
- if (!this.#isVariable()) this.#chui_tree_view.style.setProperty("--tree_view_row_height", `${this.#rowHeight()}px`);
190
- this.#chui_tree_view.dataset.virtual = "true";
191
- main_test.classList.add("tree_view_virtual");
198
+ if (!this.#isVariable())
199
+ this.#chuiTreeView.style.setProperty("--tree_view_row_height", `${this.#rowHeight()}px`);
200
+ this.#chuiTreeView.dataset.virtual = "true";
201
+ treeNode.classList.add("tree_view_virtual");
192
202
  this.#bindScroll();
193
203
  // Клик по раскрытию меняет не только классы, но и состав видимых строк
194
- main_test.addEventListener("click", this.#onStructureClick);
204
+ treeNode.addEventListener("click", this.#onStructureClick);
195
205
  }
196
206
  this.#applySelection(false);
197
207
  this.#syncDraggable();
198
- this.#chui_tree_view.appendChild(main_test)
208
+ this.#chuiTreeView.appendChild(treeNode);
199
209
  if (this.#virtual !== undefined) this.#renderVirtual();
200
210
  }
201
211
 
202
212
  set() {
203
- return this.#chui_tree_view;
213
+ return this.#chuiTreeView;
204
214
  }
205
215
  /**
206
216
  * Снять подписки (прокрутка, клавиатура, клики, drag) и отпустить модель строк: плоский список
@@ -212,28 +222,28 @@ class TreeView {
212
222
  this.#destroyed = true;
213
223
  if (this.#raf !== null) cancelAnimationFrame(this.#raf);
214
224
  this.#raf = null;
215
- this.#chui_tree_view.removeEventListener("scroll", this.#onScroll);
216
- this.#scroll_bound = false;
217
- this.#main_test.removeEventListener("click", this.#onClick);
218
- this.#main_test.removeEventListener("keydown", this.#onSelectionKeyDown, true);
219
- this.#main_test.removeEventListener("keydown", this.#onNavigationKeyDown);
220
- this.#main_test.removeEventListener("click", this.#onStructureClick);
221
- this.#main_test.removeEventListener("dragstart", this.#onDragStart);
222
- this.#main_test.removeEventListener("dragover", this.#onDragOver);
223
- this.#main_test.removeEventListener("dragleave", this.#onDragLeave);
224
- this.#main_test.removeEventListener("drop", this.#onDropEvent);
225
- this.#main_test.removeEventListener("dragend", this.#onDragEnd);
225
+ this.#chuiTreeView.removeEventListener("scroll", this.#onScroll);
226
+ this.#scrollBound = false;
227
+ this.#mainTest.removeEventListener("click", this.#onClick);
228
+ this.#mainTest.removeEventListener("keydown", this.#onSelectionKeyDown, true);
229
+ this.#mainTest.removeEventListener("keydown", this.#onNavigationKeyDown);
230
+ this.#mainTest.removeEventListener("click", this.#onStructureClick);
231
+ this.#mainTest.removeEventListener("dragstart", this.#onDragStart);
232
+ this.#mainTest.removeEventListener("dragover", this.#onDragOver);
233
+ this.#mainTest.removeEventListener("dragleave", this.#onDragLeave);
234
+ this.#mainTest.removeEventListener("drop", this.#onDropEvent);
235
+ this.#mainTest.removeEventListener("dragend", this.#onDragEnd);
226
236
  this.#nodes = [];
227
237
  this.#rows = [];
228
238
  this.#roots = [];
229
- this.#root_rows = [];
230
- this.#row_heights = [];
239
+ this.#rootRows = [];
240
+ this.#rowHeights = [];
231
241
  this.#offsets = [0];
232
- this.#node_index.clear();
233
- this.#child_rows.clear();
234
- this.#row_sizes.clear();
235
- this.#row_positions.clear();
236
- this.#subtree_heights.clear();
242
+ this.#nodeIndex.clear();
243
+ this.#childRows.clear();
244
+ this.#rowSizes.clear();
245
+ this.#rowPositions.clear();
246
+ this.#subtreeHeights.clear();
237
247
  this.#heights.clear();
238
248
  this.#selected.clear();
239
249
  return this;
@@ -241,7 +251,7 @@ class TreeView {
241
251
 
242
252
  // === ВЫБОР ===
243
253
  getSelectionMode() {
244
- return this.#selection_mode;
254
+ return this.#selectionMode;
245
255
  }
246
256
 
247
257
  /** Описания выбранных узлов — те же объекты, что вернули фабрики */
@@ -285,7 +295,9 @@ class TreeView {
285
295
  // строки могут быть вне документа, но остаются видимыми по смыслу
286
296
  #items() {
287
297
  if (this.#virtual === undefined) {
288
- return [...this.#main_test.querySelectorAll('tree_view_button')].filter(button => this.#isVisibleRow(button));
298
+ return [...this.#mainTest.querySelectorAll("tree_view_button")].filter((button) =>
299
+ this.#isVisibleRow(button)
300
+ );
289
301
  }
290
302
  return this.#project().map((row) => this.#nodes[row.index].button);
291
303
  }
@@ -308,7 +320,7 @@ class TreeView {
308
320
 
309
321
  /** Все кнопки, к которым применим выбор: в виртуальном режиме — по модели, а не по документу */
310
322
  #selectionTargets() {
311
- if (this.#virtual === undefined) return [...this.#main_test.querySelectorAll('tree_view_button')];
323
+ if (this.#virtual === undefined) return [...this.#mainTest.querySelectorAll("tree_view_button")];
312
324
  return this.#project().map((row) => this.#nodes[row.index].button);
313
325
  }
314
326
 
@@ -320,7 +332,7 @@ class TreeView {
320
332
  const node = entry instanceof Element ? entry : entry.button;
321
333
  if (node === undefined || !(node instanceof Element)) continue;
322
334
  // В виртуальном режиме строка может быть вне документа — она всё равно есть в модели
323
- if (this.#main_test.contains(node) || this.#node_index.has(node)) nodes.push(node);
335
+ if (this.#mainTest.contains(node) || this.#nodeIndex.has(node)) nodes.push(node);
324
336
  }
325
337
  return nodes;
326
338
  }
@@ -337,22 +349,25 @@ class TreeView {
337
349
  for (const node of this.#selectionTargets()) {
338
350
  const selected = this.#selected.has(node);
339
351
  node.classList.toggle(SELECTED_CLASS, selected);
340
- if (this.#selection_mode !== TreeView.SELECTION.NONE) node.setAttribute("aria-selected", selected ? "true" : "false");
352
+ if (this.#selectionMode !== TreeView.SELECTION.NONE)
353
+ node.setAttribute("aria-selected", selected ? "true" : "false");
341
354
  }
342
- if (!announce || this.#on_select === undefined) return;
355
+ if (!announce || this.#onSelect === undefined) return;
343
356
  const items = this.getSelected();
344
- this.#on_select(items, items.length === 0 ? undefined : items[items.length - 1]);
357
+ this.#onSelect(items, items.length === 0 ? undefined : items[items.length - 1]);
345
358
  }
346
359
 
347
360
  #toggleNode(node) {
348
- const nodes = this.#selected.has(node) ? [...this.#selected].filter((entry) => entry !== node) : [...this.#selected, node];
361
+ const nodes = this.#selected.has(node)
362
+ ? [...this.#selected].filter((entry) => entry !== node)
363
+ : [...this.#selected, node];
349
364
  this.#anchor = node;
350
365
  this.#commitSelection(nodes);
351
366
  }
352
367
 
353
368
  #selectRange(node) {
354
369
  const items = this.#items();
355
- const anchor = this.#anchor === undefined || !this.#main_test.contains(this.#anchor) ? node : this.#anchor;
370
+ const anchor = this.#anchor === undefined || !this.#mainTest.contains(this.#anchor) ? node : this.#anchor;
356
371
  const from = items.indexOf(anchor);
357
372
  const to = items.indexOf(node);
358
373
  if (from === -1 || to === -1) return;
@@ -386,9 +401,9 @@ class TreeView {
386
401
  #inOwnSubtree(source, target) {
387
402
  if (source === target || source.contains(target)) return true;
388
403
  // Модель знает родство и для строк вне окна: DOM-проверка видит только отрисованное
389
- const sourceIndex = this.#node_index.get(source);
404
+ const sourceIndex = this.#nodeIndex.get(source);
390
405
  if (sourceIndex !== undefined) {
391
- let current = this.#node_index.get(target);
406
+ let current = this.#nodeIndex.get(target);
392
407
  while (current !== undefined && current !== -1) {
393
408
  if (current === sourceIndex) return true;
394
409
  current = this.#nodes[current].parent;
@@ -410,13 +425,13 @@ class TreeView {
410
425
  }
411
426
 
412
427
  #applyDropTarget(target, position) {
413
- if (this.#drop_target !== undefined) {
414
- this.#drop_target.node.classList.remove(DROP_CLASSES[this.#drop_target.position]);
415
- this.#drop_target = undefined;
428
+ if (this.#dropTarget !== undefined) {
429
+ this.#dropTarget.node.classList.remove(DROP_CLASSES[this.#dropTarget.position]);
430
+ this.#dropTarget = undefined;
416
431
  }
417
432
  if (target !== undefined && position !== undefined) {
418
433
  target.classList.add(DROP_CLASSES[position]);
419
- this.#drop_target = {node: target, position: position};
434
+ this.#dropTarget = { node: target, position: position };
420
435
  }
421
436
  }
422
437
 
@@ -428,7 +443,10 @@ class TreeView {
428
443
  let reference;
429
444
  if (position === TreeView.DROP.INSIDE) {
430
445
  const panel = target.nextElementSibling;
431
- const html = panel !== null && panel.tagName.toLowerCase() === "tree_view_panel" ? panel.querySelector("tree_view_html") : null;
446
+ const html =
447
+ panel !== null && panel.tagName.toLowerCase() === "tree_view_panel"
448
+ ? panel.querySelector("tree_view_html")
449
+ : null;
432
450
  if (html === null) return false;
433
451
  container = html;
434
452
  reference = null;
@@ -444,14 +462,14 @@ class TreeView {
444
462
  }
445
463
 
446
464
  #finishDrag() {
447
- if (this.#drag_source !== undefined) this.#drag_source.classList.remove(DRAGGING_CLASS);
448
- this.#drag_source = undefined;
465
+ if (this.#dragSource !== undefined) this.#dragSource.classList.remove(DRAGGING_CLASS);
466
+ this.#dragSource = undefined;
449
467
  }
450
468
 
451
469
  #descriptor(node) {
452
470
  const item = TreeView.#registry.get(node);
453
471
  if (item !== undefined) return item;
454
- return {title: this.#title(node), button: node};
472
+ return { title: this.#title(node), button: node };
455
473
  }
456
474
 
457
475
  #title(node) {
@@ -466,9 +484,9 @@ class TreeView {
466
484
  // Строится один раз по полному DOM, ленивые поддеревья и переносы достраивают её точечно.
467
485
  #buildStructure() {
468
486
  this.#nodes = [];
469
- this.#node_index = new Map();
487
+ this.#nodeIndex = new Map();
470
488
  this.#roots = [];
471
- for (const child of [...this.#main_test.children]) {
489
+ for (const child of [...this.#mainTest.children]) {
472
490
  if (child.tagName.toLowerCase() !== "tree_view_button") continue;
473
491
  this.#register(child, -1);
474
492
  }
@@ -477,7 +495,7 @@ class TreeView {
477
495
 
478
496
  /** Структура изменилась: плоский список и окно надо пересобрать */
479
497
  #touch() {
480
- this.#rows_dirty = true;
498
+ this.#rowsDirty = true;
481
499
  this.#stamp += 1;
482
500
  }
483
501
 
@@ -495,10 +513,10 @@ class TreeView {
495
513
  parent: parent,
496
514
  children: [],
497
515
  title: this.#title(button),
498
- hidden: false
516
+ hidden: false,
499
517
  };
500
518
  this.#nodes.push(record);
501
- this.#node_index.set(button, index);
519
+ this.#nodeIndex.set(button, index);
502
520
  if (parent === -1) this.#roots.push(index);
503
521
  else this.#nodes[parent].children.push(index);
504
522
  if (html !== null) {
@@ -517,7 +535,7 @@ class TreeView {
517
535
  * можно собирать из него, не оглядываясь на документ.
518
536
  */
519
537
  #project() {
520
- if (!this.#rows_dirty) return this.#rows;
538
+ if (!this.#rowsDirty) return this.#rows;
521
539
  const rows = [];
522
540
  const positions = new Map();
523
541
  const childrenOf = new Map();
@@ -529,7 +547,7 @@ class TreeView {
529
547
  const record = this.#nodes[index];
530
548
  if (record === undefined || record.hidden) return 0;
531
549
  positions.set(index, rows.length);
532
- rows.push({index: index, depth: depth});
550
+ rows.push({ index: index, depth: depth });
533
551
  const own = variable ? this.#heightOf(index) : this.#rowHeight();
534
552
  heights.push(own);
535
553
  let size = 1;
@@ -553,17 +571,17 @@ class TreeView {
553
571
  const roots = [];
554
572
  for (const root of this.#roots) if (walk(root, 0) > 0) roots.push(root);
555
573
  this.#rows = rows;
556
- this.#child_rows = childrenOf;
557
- this.#root_rows = roots;
558
- this.#row_sizes = sizes;
559
- this.#subtree_heights = subtree;
560
- this.#row_heights = heights;
561
- this.#row_positions = positions;
574
+ this.#childRows = childrenOf;
575
+ this.#rootRows = roots;
576
+ this.#rowSizes = sizes;
577
+ this.#subtreeHeights = subtree;
578
+ this.#rowHeights = heights;
579
+ this.#rowPositions = positions;
562
580
  // Верхняя граница каждой позиции: для переменной высоты окно и прокрутка считаются по ней
563
581
  const offsets = [0];
564
582
  for (const value of heights) offsets.push(offsets[offsets.length - 1] + value);
565
583
  this.#offsets = offsets;
566
- this.#rows_dirty = false;
584
+ this.#rowsDirty = false;
567
585
  return rows;
568
586
  }
569
587
 
@@ -575,19 +593,19 @@ class TreeView {
575
593
 
576
594
  /** Переменная высота строк: высоты из данных (функция) или измеренные по DOM */
577
595
  #isVariable() {
578
- return this.#variable_height || this.#measure_dom;
596
+ return this.#variableHeight || this.#measureDom;
579
597
  }
580
598
 
581
599
  /** Высота строки с индексом модели: из данных, из кеша замеров или оценка */
582
600
  #heightOf(index) {
583
- if (this.#variable_height) {
601
+ if (this.#variableHeight) {
584
602
  if (!this.#heights.has(index)) {
585
603
  const value = Number(this.#virtual.rowHeight(this.#nodes[index], index));
586
604
  this.#heights.set(index, Number.isFinite(value) && value > 0 ? value : 26);
587
605
  }
588
606
  return this.#heights.get(index);
589
607
  }
590
- if (this.#measure_dom) {
608
+ if (this.#measureDom) {
591
609
  if (this.#heights.has(index)) return this.#heights.get(index);
592
610
  return this.#virtual.estimatedRowHeight;
593
611
  }
@@ -601,13 +619,13 @@ class TreeView {
601
619
  * тела `DataGrid`).
602
620
  */
603
621
  #measureWindow() {
604
- for (const button of this.#main_test.querySelectorAll("tree_view_button")) {
605
- const index = this.#node_index.get(button);
622
+ for (const button of this.#mainTest.querySelectorAll("tree_view_button")) {
623
+ const index = this.#nodeIndex.get(button);
606
624
  if (index === undefined) continue;
607
625
  const measured = Math.round(button.getBoundingClientRect().height);
608
626
  if (measured > 0 && this.#heights.get(index) !== measured) {
609
627
  this.#heights.set(index, measured);
610
- this.#rows_dirty = true;
628
+ this.#rowsDirty = true;
611
629
  }
612
630
  }
613
631
  }
@@ -616,12 +634,12 @@ class TreeView {
616
634
  #rowHeight() {
617
635
  if (this.#virtual === undefined) return 26;
618
636
  if (this.#virtual.rowHeight !== undefined) return this.#virtual.rowHeight;
619
- return this.#measured_row_height === undefined ? 26 : this.#measured_row_height;
637
+ return this.#measuredRowHeight === undefined ? 26 : this.#measuredRowHeight;
620
638
  }
621
639
 
622
640
  #measureRow() {
623
641
  if (this.#virtual === undefined || this.#isVariable() || this.#virtual.rowHeight !== undefined) return 0;
624
- const sample = this.#main_test.querySelector("tree_view_button");
642
+ const sample = this.#mainTest.querySelector("tree_view_button");
625
643
  return sample === null ? 0 : Math.round(sample.getBoundingClientRect().height);
626
644
  }
627
645
 
@@ -630,9 +648,9 @@ class TreeView {
630
648
  }
631
649
 
632
650
  #bindScroll() {
633
- if (this.#scroll_bound) return;
634
- this.#scroll_bound = true;
635
- this.#chui_tree_view.addEventListener("scroll", this.#onScroll, {passive: true});
651
+ if (this.#scrollBound) return;
652
+ this.#scrollBound = true;
653
+ this.#chuiTreeView.addEventListener("scroll", this.#onScroll, { passive: true });
636
654
  }
637
655
 
638
656
  #onScroll = () => {
@@ -661,15 +679,15 @@ class TreeView {
661
679
  if (remeasure) {
662
680
  // Переменный режим по DOM: сначала измеряем уже отрисованные строки — их настоящие
663
681
  // высоты уточняют смещения, и только потом считается окно
664
- if (this.#measure_dom) this.#measureWindow();
682
+ if (this.#measureDom) this.#measureWindow();
665
683
  // Настоящая высота строки может отличаться от умолчания: распорки и окно считаются
666
684
  // по ней, поэтому замер идёт до расчёта окна
667
685
  const measured = this.#measureRow();
668
- if (measured > 0 && measured !== this.#measured_row_height) {
669
- this.#measured_row_height = measured;
670
- this.#chui_tree_view.style.setProperty("--tree_view_row_height", `${measured}px`);
686
+ if (measured > 0 && measured !== this.#measuredRowHeight) {
687
+ this.#measuredRowHeight = measured;
688
+ this.#chuiTreeView.style.setProperty("--tree_view_row_height", `${measured}px`);
671
689
  // Смещения в проекции считались по прежней высоте — пересобрать
672
- this.#rows_dirty = true;
690
+ this.#rowsDirty = true;
673
691
  force = true;
674
692
  }
675
693
  }
@@ -677,7 +695,7 @@ class TreeView {
677
695
  const variable = this.#isVariable();
678
696
  const height = this.#rowHeight();
679
697
  const buffer = this.#buffer();
680
- const viewport = Math.max(1, this.#chui_tree_view.clientHeight);
698
+ const viewport = Math.max(1, this.#chuiTreeView.clientHeight);
681
699
  // Позиция строки по верхней границе прокрутки (двоичный поиск по смещениям)
682
700
  const positionFor = (top) => {
683
701
  let low = 0;
@@ -695,13 +713,14 @@ class TreeView {
695
713
  // нет паддинга, рамки и зазоров между отрисованными строками, поэтому «модельный» низ чуть
696
714
  // выше настоящего — только по нему последняя строка (End, стрелка вниз) осталась бы обрезанной
697
715
  const modelBottom = Math.max(0, total - viewport);
698
- const realBottom = Math.max(0, this.#chui_tree_view.scrollHeight - viewport);
699
- let scrollTop = Math.min(Math.max(0, this.#chui_tree_view.scrollTop), Math.max(modelBottom, realBottom));
716
+ const realBottom = Math.max(0, this.#chuiTreeView.scrollHeight - viewport);
717
+ let scrollTop = Math.min(Math.max(0, this.#chuiTreeView.scrollTop), Math.max(modelBottom, realBottom));
700
718
  // Низ окна для переменной высоты — позиция строки под нижней кромкой плюс одна строка,
701
719
  // иначе последняя видимая строка выпадала бы из окна
702
- const windowTo = (top) => variable
703
- ? Math.min(rows.length, positionFor(top + viewport) + 1 + buffer)
704
- : Math.min(rows.length, Math.ceil((top + viewport) / height) + buffer);
720
+ const windowTo = (top) =>
721
+ variable
722
+ ? Math.min(rows.length, positionFor(top + viewport) + 1 + buffer)
723
+ : Math.min(rows.length, Math.ceil((top + viewport) / height) + buffer);
705
724
  let from = Math.max(0, positionFor(scrollTop) - buffer);
706
725
  let to = windowTo(scrollTop);
707
726
  // Список сжался (поиск, сворачивание) — позиция ещё старая и указывает за конец: прижимаем
@@ -711,14 +730,21 @@ class TreeView {
711
730
  from = Math.max(0, positionFor(scrollTop) - buffer);
712
731
  to = windowTo(scrollTop);
713
732
  }
714
- if (scrollTop !== this.#chui_tree_view.scrollTop) this.#chui_tree_view.scrollTop = scrollTop;
733
+ if (scrollTop !== this.#chuiTreeView.scrollTop) this.#chuiTreeView.scrollTop = scrollTop;
715
734
  // Ни окно, ни структура не изменились (прокрутка в пределах строки) — пересобирать
716
735
  // нечего: пересборка на каждом кадре только дёргала бы DOM и фокус
717
- if (!force && this.#win !== undefined && from === this.#win.from && to === this.#win.to
718
- && this.#win_rows === rows.length && this.#win_stamp === this.#stamp) return;
736
+ if (
737
+ !force &&
738
+ this.#win !== undefined &&
739
+ from === this.#win.from &&
740
+ to === this.#win.to &&
741
+ this.#winRows === rows.length &&
742
+ this.#winStamp === this.#stamp
743
+ )
744
+ return;
719
745
  // Кто держит фокус: снимок до мутации — после неё `document.activeElement` уже `body`
720
746
  const active = document.activeElement;
721
- const activeIndex = active instanceof Element ? this.#node_index.get(active) : undefined;
747
+ const activeIndex = active instanceof Element ? this.#nodeIndex.get(active) : undefined;
722
748
  const keepIndex = activeIndex === undefined ? undefined : this.#pinRow(rows, activeIndex);
723
749
  const visible = new Set();
724
750
  for (let position = from; position < to; position++) {
@@ -737,7 +763,7 @@ class TreeView {
737
763
  current = this.#nodes[current].parent;
738
764
  }
739
765
  }
740
- const sizeOf = (index) => this.#row_sizes.get(index) || 1;
766
+ const sizeOf = (index) => this.#rowSizes.get(index) || 1;
741
767
  const spacer = (px) => {
742
768
  const node = document.createElement("tree_view_spacer");
743
769
  node.setAttribute("aria-hidden", "true");
@@ -745,10 +771,10 @@ class TreeView {
745
771
  return node;
746
772
  };
747
773
  // Сколько занимает пропущенная ветка: в переменном режиме — суммарная высота её поддерева
748
- const subtreePx = (index) => variable ? (this.#subtree_heights.get(index) || 0) : sizeOf(index) * height;
774
+ const subtreePx = (index) => (variable ? this.#subtreeHeights.get(index) || 0 : sizeOf(index) * height);
749
775
  const fill = (container, parentIndex) => {
750
776
  // Дети берутся из проекции: скрытое поиском и свёрнутое в неё уже не попадает
751
- const children = parentIndex === -1 ? this.#root_rows : (this.#child_rows.get(parentIndex) || []);
777
+ const children = parentIndex === -1 ? this.#rootRows : this.#childRows.get(parentIndex) || [];
752
778
  const parts = [];
753
779
  let gap = 0;
754
780
  for (const index of children) {
@@ -763,7 +789,7 @@ class TreeView {
763
789
  }
764
790
  const record = this.#nodes[index];
765
791
  // Высота из данных задаётся строке инлайном — иначе DOM разошёлся бы с моделью
766
- if (this.#variable_height) {
792
+ if (this.#variableHeight) {
767
793
  const rowHeightPx = this.#heightOf(index);
768
794
  record.button.style.minHeight = `${rowHeightPx}px`;
769
795
  record.button.style.height = `${rowHeightPx}px`;
@@ -783,14 +809,14 @@ class TreeView {
783
809
  else record.html.replaceChildren();
784
810
  }
785
811
  };
786
- fill(this.#main_test, -1);
787
- if (this.#status !== undefined) this.#main_test.appendChild(this.#status);
812
+ fill(this.#mainTest, -1);
813
+ if (this.#status !== undefined) this.#mainTest.appendChild(this.#status);
788
814
  this.#restoreFocus(keepIndex);
789
815
  this.#applySelection(false);
790
816
  this.#syncDraggable();
791
- this.#win = {from: from, to: to};
792
- this.#win_stamp = this.#stamp;
793
- this.#win_rows = rows.length;
817
+ this.#win = { from: from, to: to };
818
+ this.#winStamp = this.#stamp;
819
+ this.#winRows = rows.length;
794
820
  }
795
821
 
796
822
  /**
@@ -799,7 +825,7 @@ class TreeView {
799
825
  * видимая: фокус остаётся в дереве, а не уходит в `body`.
800
826
  */
801
827
  #pinRow(rows, index) {
802
- const position = this.#row_positions.get(index);
828
+ const position = this.#rowPositions.get(index);
803
829
  if (position !== undefined) return rows[position].index;
804
830
  let nearest = undefined;
805
831
  for (const row of rows) {
@@ -818,14 +844,18 @@ class TreeView {
818
844
  if (index === undefined) return;
819
845
  const record = this.#nodes[index];
820
846
  if (record === undefined || document.activeElement === record.button) return;
821
- record.button.focus({preventScroll: true});
847
+ record.button.focus({ preventScroll: true });
822
848
  }
823
849
 
824
850
  /** Что сейчас в окне: {from, to} по плоскому списку (для стендов и приложений) */
825
- getVirtualWindow() { return this.#win; }
851
+ getVirtualWindow() {
852
+ return this.#win;
853
+ }
826
854
 
827
855
  /** Переменная высота строк включена (`rowHeight`-функция или `variableHeight`) */
828
- isVariableHeight() { return this.#isVariable(); }
856
+ isVariableHeight() {
857
+ return this.#isVariable();
858
+ }
829
859
 
830
860
  /** Высота строки с индексом модели: из данных, из замеров или заданная */
831
861
  getRowHeightAt(index = Number()) {
@@ -851,17 +881,17 @@ class TreeView {
851
881
  return;
852
882
  }
853
883
  // Позиция строки в плоском списке: карта проекции, чтобы не перебирать все строки
854
- const modelIndex = this.#node_index.get(button);
855
- const place = modelIndex === undefined ? undefined : this.#row_positions.get(modelIndex);
884
+ const modelIndex = this.#nodeIndex.get(button);
885
+ const place = modelIndex === undefined ? undefined : this.#rowPositions.get(modelIndex);
856
886
  if (place === undefined) return;
857
887
  // В переменном режиме верх и высота строки берутся из смещений проекции
858
888
  const variable = this.#isVariable();
859
- const height = variable ? (this.#row_heights[place] || this.#rowHeight()) : this.#rowHeight();
860
- const viewport = Math.max(1, this.#chui_tree_view.clientHeight);
889
+ const height = variable ? this.#rowHeights[place] || this.#rowHeight() : this.#rowHeight();
890
+ const viewport = Math.max(1, this.#chuiTreeView.clientHeight);
861
891
  const top = variable ? this.#offsets[place] : place * height;
862
- const scrollTop = this.#chui_tree_view.scrollTop;
892
+ const scrollTop = this.#chuiTreeView.scrollTop;
863
893
  if (top < scrollTop || top + height > scrollTop + viewport) {
864
- this.#chui_tree_view.scrollTop = Math.max(0, top - Math.floor(viewport / 2));
894
+ this.#chuiTreeView.scrollTop = Math.max(0, top - Math.floor(viewport / 2));
865
895
  this.#renderVirtual();
866
896
  }
867
897
  button.focus();
@@ -871,7 +901,7 @@ class TreeView {
871
901
  #onStructureClick = (event) => {
872
902
  const button = event.target instanceof Element ? event.target.closest("tree_view_button[aria-expanded]") : null;
873
903
  if (button === null) return;
874
- const index = this.#node_index.get(button);
904
+ const index = this.#nodeIndex.get(button);
875
905
  if (index !== undefined) this.#syncSubtree(index);
876
906
  const panel = button.nextElementSibling;
877
907
  if (panel !== null && panel.tagName.toLowerCase() === "tree_view_panel") {
@@ -887,7 +917,7 @@ class TreeView {
887
917
  if (record === undefined || record.html === null) return;
888
918
  for (const child of [...record.html.children]) {
889
919
  if (child.tagName.toLowerCase() !== "tree_view_button") continue;
890
- if (this.#node_index.has(child)) continue;
920
+ if (this.#nodeIndex.has(child)) continue;
891
921
  this.#register(child, index);
892
922
  }
893
923
  }
@@ -907,8 +937,8 @@ class TreeView {
907
937
  }
908
938
 
909
939
  #syncMove(source, target, position) {
910
- const index = this.#node_index.get(source);
911
- const targetIndex = this.#node_index.get(target);
940
+ const index = this.#nodeIndex.get(source);
941
+ const targetIndex = this.#nodeIndex.get(target);
912
942
  if (index === undefined || targetIndex === undefined) return;
913
943
  if (position === TreeView.DROP.INSIDE) this.#relink(index, targetIndex);
914
944
  else {
@@ -921,17 +951,23 @@ class TreeView {
921
951
  }
922
952
 
923
953
  // === ПОИСК ===
924
- getSearchQuery() { return this.#query; }
925
- isSearching() { return this.#query !== ""; }
954
+ getSearchQuery() {
955
+ return this.#query;
956
+ }
957
+ isSearching() {
958
+ return this.#query !== "";
959
+ }
926
960
  /** Совпадения поиска (описания из фабрик), в порядке показа */
927
- getMatches() { return [...this.#matches].map((node) => this.#descriptor(node)); }
961
+ getMatches() {
962
+ return [...this.#matches].map((node) => this.#descriptor(node));
963
+ }
928
964
 
929
965
  /**
930
966
  * Показать только подходящие узлы и цепочку их предков; остальное убирается (`display: none`),
931
967
  * поэтому навигация и выбор дальше работают по видимому. `autoExpand` раскрывает предков
932
968
  * (без него совпадения внутри свёрнутых веток остаются скрытыми), `highlight` подсвечивает совпадения.
933
969
  */
934
- search(query = String(), options = {autoExpand: true, highlight: true}) {
970
+ search(query = String(), options = { autoExpand: true, highlight: true }) {
935
971
  this.clearSearch();
936
972
  const text = String(query).trim().toLocaleLowerCase();
937
973
  if (text === "") return this;
@@ -939,10 +975,10 @@ class TreeView {
939
975
  // В виртуальном режиме в документе только окно — ищем и скрываем по модели
940
976
  if (this.#virtual !== undefined) {
941
977
  this.#searchModel(text, options);
942
- this.#announce(I18n.t("treeView.found", {count: this.#matches.size}));
978
+ this.#announce(I18n.t("treeView.found", { count: this.#matches.size }));
943
979
  return this;
944
980
  }
945
- const buttons = [...this.#main_test.querySelectorAll("tree_view_button")];
981
+ const buttons = [...this.#mainTest.querySelectorAll("tree_view_button")];
946
982
  const visible = new Set();
947
983
  for (const button of buttons) {
948
984
  if (!this.#title(button).toLocaleLowerCase().includes(text)) continue;
@@ -964,7 +1000,7 @@ class TreeView {
964
1000
  }
965
1001
  }
966
1002
  for (const button of buttons) if (!visible.has(button)) button.classList.add(TreeView.#HIDDEN_CLASS);
967
- for (const panel of this.#main_test.querySelectorAll("tree_view_panel")) {
1003
+ for (const panel of this.#mainTest.querySelectorAll("tree_view_panel")) {
968
1004
  if (!visible.has(panel)) panel.classList.add(TreeView.#HIDDEN_CLASS);
969
1005
  // Раскрытая панель должна быть видна целиком: max-height после раскрытия пересчитываем
970
1006
  else if (panel.style.maxHeight !== "") panel.style.maxHeight = "max-content";
@@ -972,7 +1008,7 @@ class TreeView {
972
1008
  if (options.highlight !== false) for (const node of this.#matches) node.classList.add("tree_view_button_match");
973
1009
  this.#touch();
974
1010
  if (this.#virtual !== undefined) this.#renderVirtual();
975
- this.#announce(I18n.t("treeView.found", {count: this.#matches.size}));
1011
+ this.#announce(I18n.t("treeView.found", { count: this.#matches.size }));
976
1012
  return this;
977
1013
  }
978
1014
 
@@ -1000,7 +1036,9 @@ class TreeView {
1000
1036
  return this;
1001
1037
  }
1002
1038
 
1003
- collapse(target) { return this.expand(target, false); }
1039
+ collapse(target) {
1040
+ return this.expand(target, false);
1041
+ }
1004
1042
 
1005
1043
  /** Раскрыть всё: в виртуальном режиме — единственный способ, строки вне окна не кликнуть */
1006
1044
  expandAll() {
@@ -1014,7 +1052,8 @@ class TreeView {
1014
1052
  }
1015
1053
 
1016
1054
  getExpanded() {
1017
- return this.#nodes.filter((record) => record.button.getAttribute("aria-expanded") === "true")
1055
+ return this.#nodes
1056
+ .filter((record) => record.button.getAttribute("aria-expanded") === "true")
1018
1057
  .map((record) => this.#descriptor(record.button));
1019
1058
  }
1020
1059
 
@@ -1022,7 +1061,7 @@ class TreeView {
1022
1061
  if (target === undefined || target === null) return undefined;
1023
1062
  const node = target instanceof Element ? target : target.button;
1024
1063
  if (!(node instanceof Element)) return undefined;
1025
- const index = this.#node_index.get(node);
1064
+ const index = this.#nodeIndex.get(node);
1026
1065
  return index === undefined ? undefined : this.#nodes[index];
1027
1066
  }
1028
1067
 
@@ -1045,14 +1084,16 @@ class TreeView {
1045
1084
  }
1046
1085
  }
1047
1086
  for (const record of this.#nodes) record.hidden = !visible.has(record.index);
1048
- if (options.highlight !== false) for (const button of this.#matches) button.classList.add("tree_view_button_match");
1087
+ if (options.highlight !== false)
1088
+ for (const button of this.#matches) button.classList.add("tree_view_button_match");
1049
1089
  this.#touch();
1050
1090
  this.#renderVirtual();
1051
1091
  }
1052
1092
 
1053
1093
  /** Снять фильтр поиска: возвращает все узлы и снимает подсветку */
1054
1094
  clearSearch() {
1055
- for (const node of this.#main_test.querySelectorAll("." + TreeView.#HIDDEN_CLASS)) node.classList.remove(TreeView.#HIDDEN_CLASS);
1095
+ for (const node of this.#mainTest.querySelectorAll("." + TreeView.#HIDDEN_CLASS))
1096
+ node.classList.remove(TreeView.#HIDDEN_CLASS);
1056
1097
  for (const node of this.#matches) node.classList.remove("tree_view_button_match");
1057
1098
  for (const record of this.#nodes) record.hidden = false;
1058
1099
  const wasSearching = this.#query !== "";
@@ -1067,10 +1108,12 @@ class TreeView {
1067
1108
  // Статус поиска читает скринридер (узла нет, пока поиск не использовался)
1068
1109
  #announce(text = String()) {
1069
1110
  if (this.#status === undefined) {
1070
- this.#status = Object.assign(document.createElement("chui_visually_hidden"), {className: "chui_visually_hidden"});
1111
+ this.#status = Object.assign(document.createElement("chui_visually_hidden"), {
1112
+ className: "chui_visually_hidden",
1113
+ });
1071
1114
  this.#status.setAttribute("role", "status");
1072
1115
  this.#status.setAttribute("aria-live", "polite");
1073
- this.#main_test.appendChild(this.#status);
1116
+ this.#mainTest.appendChild(this.#status);
1074
1117
  }
1075
1118
  this.#status.innerText = text;
1076
1119
  }
@@ -1078,8 +1121,8 @@ class TreeView {
1078
1121
  // === ОБРАБОТЧИКИ ===
1079
1122
  #onClick = (event) => {
1080
1123
  const node = event.target instanceof Element ? event.target.closest("tree_view_button") : null;
1081
- if (node === null || !this.#main_test.contains(node)) return;
1082
- const multiple = this.#selection_mode === TreeView.SELECTION.MULTIPLE;
1124
+ if (node === null || !this.#mainTest.contains(node)) return;
1125
+ const multiple = this.#selectionMode === TreeView.SELECTION.MULTIPLE;
1083
1126
  if (multiple && (event.ctrlKey || event.metaKey)) this.#toggleNode(node);
1084
1127
  else if (multiple && event.shiftKey) this.#selectRange(node);
1085
1128
  else {
@@ -1097,16 +1140,16 @@ class TreeView {
1097
1140
  if (!event.ctrlKey && !event.metaKey && !event.shiftKey) return;
1098
1141
  const target = event.target instanceof Element ? event.target : document.activeElement;
1099
1142
  const node = target instanceof Element ? target.closest("tree_view_button") : null;
1100
- if (node === null || !this.#main_test.contains(node)) return;
1143
+ if (node === null || !this.#mainTest.contains(node)) return;
1101
1144
  event.preventDefault();
1102
1145
  event.stopPropagation();
1103
- if (event.shiftKey && this.#selection_mode === TreeView.SELECTION.MULTIPLE) this.#selectRange(node);
1146
+ if (event.shiftKey && this.#selectionMode === TreeView.SELECTION.MULTIPLE) this.#selectRange(node);
1104
1147
  else this.#toggleNode(node);
1105
1148
  };
1106
1149
 
1107
1150
  #onNavigationKeyDown = (event) => {
1108
1151
  const items = this.#items();
1109
- const current = items.findIndex(item => item === document.activeElement);
1152
+ const current = items.findIndex((item) => item === document.activeElement);
1110
1153
  if (current === -1) return;
1111
1154
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1112
1155
  if (event.altKey && this.#draggable) {
@@ -1126,7 +1169,7 @@ class TreeView {
1126
1169
  document.activeElement.click();
1127
1170
  }
1128
1171
  } else if (event.key === "ArrowLeft") {
1129
- const parent = document.activeElement.parentElement.closest('tree_view_button[aria-expanded]');
1172
+ const parent = document.activeElement.parentElement.closest("tree_view_button[aria-expanded]");
1130
1173
  if (document.activeElement.getAttribute("aria-expanded") === "true") {
1131
1174
  event.preventDefault();
1132
1175
  document.activeElement.click();
@@ -1140,7 +1183,7 @@ class TreeView {
1140
1183
  // Клавиатурная альтернатива перетаскиванию: Alt+↑/↓ переставляет узел среди соседей.
1141
1184
  // Соседей берём из модели: в виртуальном режиме в DOM только окно
1142
1185
  #moveByKeyboard(node, step) {
1143
- const index = this.#node_index.get(node);
1186
+ const index = this.#nodeIndex.get(node);
1144
1187
  if (index === undefined) return;
1145
1188
  const parent = this.#nodes[index].parent;
1146
1189
  const siblings = parent === -1 ? this.#roots : this.#nodes[parent].children;
@@ -1149,14 +1192,14 @@ class TreeView {
1149
1192
  const target = this.#nodes[targetIndex].button;
1150
1193
  const position = step < 0 ? TreeView.DROP.BEFORE : TreeView.DROP.AFTER;
1151
1194
  if (!this.#moveNodes(node, target, position)) return;
1152
- if (this.#on_drop !== undefined) this.#on_drop(this.#descriptor(node), this.#descriptor(target), position);
1195
+ if (this.#onDrop !== undefined) this.#onDrop(this.#descriptor(node), this.#descriptor(target), position);
1153
1196
  node.focus();
1154
1197
  }
1155
1198
 
1156
1199
  #onDragStart = (event) => {
1157
1200
  const node = event.target instanceof Element ? event.target.closest("tree_view_button") : null;
1158
1201
  if (node === null) return;
1159
- this.#drag_source = node;
1202
+ this.#dragSource = node;
1160
1203
  node.classList.add(DRAGGING_CLASS);
1161
1204
  if (event.dataTransfer === null) return;
1162
1205
  event.dataTransfer.effectAllowed = "move";
@@ -1164,10 +1207,10 @@ class TreeView {
1164
1207
  };
1165
1208
 
1166
1209
  #onDragOver = (event) => {
1167
- if (this.#drag_source === undefined) return;
1210
+ if (this.#dragSource === undefined) return;
1168
1211
  const target = event.target instanceof Element ? event.target.closest("tree_view_button") : null;
1169
1212
  // В себя и в собственное поддерево бросать нельзя: узел потерял бы ветку
1170
- if (target === null || this.#inOwnSubtree(this.#drag_source, target)) {
1213
+ if (target === null || this.#inOwnSubtree(this.#dragSource, target)) {
1171
1214
  this.#applyDropTarget(undefined, undefined);
1172
1215
  if (event.dataTransfer !== null) event.dataTransfer.dropEffect = "none";
1173
1216
  return;
@@ -1178,20 +1221,20 @@ class TreeView {
1178
1221
  };
1179
1222
 
1180
1223
  #onDragLeave = (event) => {
1181
- if (event.relatedTarget instanceof Element && this.#main_test.contains(event.relatedTarget)) return;
1224
+ if (event.relatedTarget instanceof Element && this.#mainTest.contains(event.relatedTarget)) return;
1182
1225
  this.#applyDropTarget(undefined, undefined);
1183
1226
  };
1184
1227
 
1185
1228
  #onDropEvent = (event) => {
1186
- if (this.#drag_source === undefined || this.#drop_target === undefined) return;
1229
+ if (this.#dragSource === undefined || this.#dropTarget === undefined) return;
1187
1230
  event.preventDefault();
1188
- const source = this.#drag_source;
1189
- const target = this.#drop_target.node;
1190
- const position = this.#drop_target.position;
1231
+ const source = this.#dragSource;
1232
+ const target = this.#dropTarget.node;
1233
+ const position = this.#dropTarget.position;
1191
1234
  this.#applyDropTarget(undefined, undefined);
1192
1235
  this.#finishDrag();
1193
- if (this.#move_on_drop) this.#moveNodes(source, target, position);
1194
- if (this.#on_drop !== undefined) this.#on_drop(this.#descriptor(source), this.#descriptor(target), position);
1236
+ if (this.#moveOnDrop) this.#moveNodes(source, target, position);
1237
+ if (this.#onDrop !== undefined) this.#onDrop(this.#descriptor(source), this.#descriptor(target), position);
1195
1238
  };
1196
1239
 
1197
1240
  #onDragEnd = () => {
@@ -1200,88 +1243,90 @@ class TreeView {
1200
1243
  };
1201
1244
 
1202
1245
  static Button(options = { title: String(), listener: () => {} }) {
1203
- let button = document.createElement('tree_view_button');
1246
+ const button = document.createElement("tree_view_button");
1204
1247
  if (options.listener !== undefined) button.addEventListener("click", options.listener);
1205
1248
  makeKeyboardButton(button, "treeitem");
1206
- let button_text = document.createElement('tree_view_button_text');
1207
- button_text.innerHTML = options.title;
1208
- button.appendChild(button_text)
1209
- const item = {title: options.title, button: button};
1249
+ const buttonText = document.createElement("tree_view_button_text");
1250
+ // Заголовок — текст, а не разметка: в `title` приходят имена файлов и папок, а тот же
1251
+ // узел читается как `textContent` (`#title`), и поиск сравнивал бы текст с тегами
1252
+ buttonText.textContent = options.title;
1253
+ button.appendChild(buttonText);
1254
+ const item = { title: options.title, button: button };
1210
1255
  TreeView.#registry.set(button, item);
1211
1256
  return item;
1212
1257
  }
1213
1258
 
1214
1259
  static ExpandButton(options = { title: String(), subButtons: [], components: [], lazySubButtons: undefined }) {
1215
- let button = document.createElement('tree_view_button');
1216
- let panel = document.createElement('tree_view_panel')
1260
+ const button = document.createElement("tree_view_button");
1261
+ const panel = document.createElement("tree_view_panel");
1217
1262
  // Раскрытая панель переходит с фиксированной высоты на max-content: иначе высота
1218
1263
  // остаётся «замороженной» после первого раскрытия
1219
1264
  const transitionEndEvent = (event) => {
1220
1265
  const target = event.currentTarget;
1221
1266
  if (target.style.maxHeight && target.style.maxHeight !== "0px") target.style.maxHeight = "max-content";
1222
1267
  };
1223
- panel.id = "chui_tree_panel_" + (tree_panel_counter += 1);
1268
+ panel.id = "chui_tree_panel_" + (treePanelCounter += 1);
1224
1269
  button.setAttribute("aria-controls", panel.id);
1225
1270
  button.setAttribute("aria-expanded", "false");
1226
1271
  makeKeyboardButton(button, "treeitem");
1227
- button.addEventListener('click', () => {
1272
+ button.addEventListener("click", () => {
1228
1273
  buildSubs();
1229
- const open = !button.classList.contains('tree_view_button_active');
1230
- button.classList.toggle('tree_view_button_active', open);
1274
+ const open = !button.classList.contains("tree_view_button_active");
1275
+ button.classList.toggle("tree_view_button_active", open);
1231
1276
  // Стрелка поворачивается по aria-expanded (styles.css) — состояние одно
1232
1277
  button.setAttribute("aria-expanded", open ? "true" : "false");
1233
1278
  if (open) {
1234
1279
  panel.style.maxHeight = panel.scrollHeight + "px";
1235
- panel.addEventListener('transitionend', transitionEndEvent);
1280
+ panel.addEventListener("transitionend", transitionEndEvent);
1236
1281
  } else {
1237
1282
  // Сначала фиксируем текущую высоту, иначе сжатие не анимируется
1238
1283
  panel.style.maxHeight = panel.scrollHeight + "px";
1239
1284
  void panel.offsetHeight;
1240
1285
  panel.style.maxHeight = "0px";
1241
1286
  }
1242
- })
1243
- let button_text = document.createElement('tree_view_button_text');
1244
- button_text.innerHTML = options.title;
1245
- button.appendChild(button_text)
1246
- let barrow = document.createElement('tree_view_button_arrow')
1287
+ });
1288
+ const buttonText = document.createElement("tree_view_button_text");
1289
+ buttonText.textContent = options.title;
1290
+ button.appendChild(buttonText);
1291
+ const barrow = document.createElement("tree_view_button_arrow");
1247
1292
  barrow.innerHTML = new Icon(Icons.HARDWARE.KEYBOARD_ARROW_DOWN).getHTML();
1248
- button.appendChild(barrow)
1249
- let html = document.createElement('tree_view_html')
1293
+ button.appendChild(barrow);
1294
+ const html = document.createElement("tree_view_html");
1250
1295
  // Ленивое поддерево: содержимое строится один раз — при первом раскрытии,
1251
1296
  // поэтому большое дерево не занимает DOM заранее (lazySubButtons: () => [...])
1252
- let subs_built = false;
1297
+ let subButtonsBuilt = false;
1253
1298
  const buildSubs = () => {
1254
- if (subs_built) return;
1255
- subs_built = true;
1299
+ if (subButtonsBuilt) return;
1300
+ subButtonsBuilt = true;
1256
1301
  if (options.subButtons !== undefined) {
1257
- options.subButtons.forEach(sub => {
1258
- html.appendChild(sub.button)
1302
+ options.subButtons.forEach((sub) => {
1303
+ html.appendChild(sub.button);
1259
1304
  if (sub.panel !== undefined) {
1260
- html.appendChild(sub.panel)
1305
+ html.appendChild(sub.panel);
1261
1306
  }
1262
- })
1307
+ });
1263
1308
  }
1264
1309
  if (options.components !== undefined) {
1265
- options.components.forEach(sub => {
1266
- html.appendChild(sub.set())
1267
- })
1310
+ options.components.forEach((sub) => {
1311
+ html.appendChild(sub.set());
1312
+ });
1268
1313
  }
1269
1314
  if (options.lazySubButtons !== undefined) {
1270
- options.lazySubButtons().forEach(sub => {
1271
- html.appendChild(sub.button)
1315
+ options.lazySubButtons().forEach((sub) => {
1316
+ html.appendChild(sub.button);
1272
1317
  if (sub.panel !== undefined) {
1273
- html.appendChild(sub.panel)
1318
+ html.appendChild(sub.panel);
1274
1319
  }
1275
- })
1320
+ });
1276
1321
  }
1277
1322
  };
1278
1323
  // Без ленивой фабрики поведение прежнее — строим сразу
1279
1324
  if (options.lazySubButtons === undefined) buildSubs();
1280
- panel.appendChild(html)
1281
- const item = {title: options.title, button: button, panel: panel};
1325
+ panel.appendChild(html);
1326
+ const item = { title: options.title, button: button, panel: panel };
1282
1327
  TreeView.#registry.set(button, item);
1283
1328
  return item;
1284
1329
  }
1285
1330
  }
1286
1331
 
1287
- exports.TreeView = TreeView
1332
+ exports.TreeView = TreeView;