chuijs 4.0.1 → 4.0.3

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 (222) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +43 -632
  3. package/framework/appLayout/default/chui_app_layout.js +314 -273
  4. package/framework/appLayout/default/global_style.css +93 -53
  5. package/framework/components/chui_accordion/accordion.js +29 -26
  6. package/framework/components/chui_accordion/styles.css +6 -5
  7. package/framework/components/chui_action_sheet/action_sheet.js +59 -33
  8. package/framework/components/chui_action_sheet/styles.css +2 -1
  9. package/framework/components/chui_affix/affix.js +11 -6
  10. package/framework/components/chui_alert/alert.js +23 -14
  11. package/framework/components/chui_avatar/avatar.js +23 -22
  12. package/framework/components/chui_avatar_group/avatar_group.js +21 -19
  13. package/framework/components/chui_backtop/backtop.js +35 -19
  14. package/framework/components/chui_badge/badge.js +15 -13
  15. package/framework/components/chui_breadcrumb/breadcrumb.js +15 -13
  16. package/framework/components/chui_button/button.js +42 -22
  17. package/framework/components/chui_button/styles.css +2 -2
  18. package/framework/components/chui_calendar/calendar.js +12 -10
  19. package/framework/components/chui_card/card.js +43 -23
  20. package/framework/components/chui_chat/chat.js +72 -33
  21. package/framework/components/chui_clipboard_history/clipboard_history.js +30 -21
  22. package/framework/components/chui_clipboard_history/styles.css +4 -2
  23. package/framework/components/chui_code/code.js +2 -2
  24. package/framework/components/chui_code/styles.css +2 -2
  25. package/framework/components/chui_code_editor/code_editor.js +47 -31
  26. package/framework/components/chui_code_editor/styles.css +3 -2
  27. package/framework/components/chui_command_palette/command_palette.js +60 -37
  28. package/framework/components/chui_console/console.js +5 -5
  29. package/framework/components/chui_console/styles.css +1 -1
  30. package/framework/components/chui_content_block/content_block.js +33 -19
  31. package/framework/components/chui_content_block/styles.css +2 -2
  32. package/framework/components/chui_context_menu/context_menu.js +9 -4
  33. package/framework/components/chui_custom_element/custom_element.js +22 -15
  34. package/framework/components/chui_data_grid/data_grid.js +396 -234
  35. package/framework/components/chui_data_grid/styles.css +10 -8
  36. package/framework/components/chui_descriptions/descriptions.js +34 -17
  37. package/framework/components/chui_details/details.js +32 -26
  38. package/framework/components/chui_details/styles.css +5 -4
  39. package/framework/components/chui_diff_viewer/diff_viewer.js +91 -66
  40. package/framework/components/chui_diff_viewer/styles.css +1 -1
  41. package/framework/components/chui_divider/divider.js +13 -11
  42. package/framework/components/chui_drawer/drawer.js +27 -25
  43. package/framework/components/chui_drop_zone/drop_zone.js +31 -21
  44. package/framework/components/chui_empty_state/empty_state.js +20 -15
  45. package/framework/components/chui_fieldset/fieldset.js +37 -20
  46. package/framework/components/chui_fieldset/styles.css +1 -1
  47. package/framework/components/chui_file_explorer/file_explorer.js +157 -86
  48. package/framework/components/chui_file_explorer/styles.css +2 -1
  49. package/framework/components/chui_form/form.js +31 -19
  50. package/framework/components/chui_form/styles.css +1 -1
  51. package/framework/components/chui_gantt/gantt.js +64 -40
  52. package/framework/components/chui_graphs/graphs.js +301 -200
  53. package/framework/components/chui_graphs/styles.css +1 -1
  54. package/framework/components/chui_hero/hero.js +40 -22
  55. package/framework/components/chui_html_block/html_block.js +6 -6
  56. package/framework/components/chui_html_block/styles.css +2 -2
  57. package/framework/components/chui_hx/hx.js +3 -3
  58. package/framework/components/chui_hx/styles.css +11 -4
  59. package/framework/components/chui_icons/icons.js +2109 -2109
  60. package/framework/components/chui_icons/styles.css +2 -2
  61. package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
  62. package/framework/components/chui_inputs/chui_check_box/styles.css +18 -18
  63. package/framework/components/chui_inputs/chui_chips/chips.js +50 -40
  64. package/framework/components/chui_inputs/chui_chips/styles.css +4 -2
  65. package/framework/components/chui_inputs/chui_color_picker/color_picker.js +36 -24
  66. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +100 -82
  67. package/framework/components/chui_inputs/chui_combo_box/styles.css +14 -12
  68. package/framework/components/chui_inputs/chui_date/date.js +102 -66
  69. package/framework/components/chui_inputs/chui_date/styles.css +12 -11
  70. package/framework/components/chui_inputs/chui_date_range/date_range.js +40 -29
  71. package/framework/components/chui_inputs/chui_datetime/datetime.js +35 -27
  72. package/framework/components/chui_inputs/chui_email/email.js +43 -31
  73. package/framework/components/chui_inputs/chui_email/styles.css +5 -5
  74. package/framework/components/chui_inputs/chui_file/file.js +68 -41
  75. package/framework/components/chui_inputs/chui_file/styles.css +13 -12
  76. package/framework/components/chui_inputs/chui_hotkey/hotkey.js +61 -36
  77. package/framework/components/chui_inputs/chui_masked/masked.js +39 -28
  78. package/framework/components/chui_inputs/chui_mention/mention.js +77 -30
  79. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +173 -153
  80. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +9 -7
  81. package/framework/components/chui_inputs/chui_number/number.js +58 -41
  82. package/framework/components/chui_inputs/chui_number/styles.css +7 -7
  83. package/framework/components/chui_inputs/chui_otp/otp.js +38 -29
  84. package/framework/components/chui_inputs/chui_password/password.js +58 -36
  85. package/framework/components/chui_inputs/chui_password/styles.css +8 -8
  86. package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
  87. package/framework/components/chui_inputs/chui_radio_button/styles.css +20 -20
  88. package/framework/components/chui_inputs/chui_rating/rating.js +26 -22
  89. package/framework/components/chui_inputs/chui_select_box/select_box.js +102 -78
  90. package/framework/components/chui_inputs/chui_select_box/styles.css +11 -11
  91. package/framework/components/chui_inputs/chui_slider/slider.js +29 -26
  92. package/framework/components/chui_inputs/chui_slider/styles.css +2 -1
  93. package/framework/components/chui_inputs/chui_text/styles.css +8 -8
  94. package/framework/components/chui_inputs/chui_text/text.js +65 -44
  95. package/framework/components/chui_inputs/chui_text_area/styles.css +6 -6
  96. package/framework/components/chui_inputs/chui_text_area/text_area.js +51 -28
  97. package/framework/components/chui_inputs/chui_time/styles.css +5 -3
  98. package/framework/components/chui_inputs/chui_time/time.js +38 -27
  99. package/framework/components/chui_inputs/chui_toggle/styles.css +3 -3
  100. package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
  101. package/framework/components/chui_json_editor/json_editor.js +53 -43
  102. package/framework/components/chui_json_viewer/json_viewer.js +52 -33
  103. package/framework/components/chui_json_viewer/styles.css +3 -2
  104. package/framework/components/chui_kanban/kanban.js +52 -34
  105. package/framework/components/chui_kbd/kbd.js +12 -10
  106. package/framework/components/chui_label/label.js +14 -12
  107. package/framework/components/chui_label/styles.css +2 -2
  108. package/framework/components/chui_link/link.js +27 -20
  109. package/framework/components/chui_list/list.js +31 -27
  110. package/framework/components/chui_list/styles.css +2 -1
  111. package/framework/components/chui_markdown_editor/markdown_editor.js +60 -44
  112. package/framework/components/chui_media/audio.js +326 -227
  113. package/framework/components/chui_media/audio_styles.css +40 -21
  114. package/framework/components/chui_media/image.js +70 -38
  115. package/framework/components/chui_media/image_compare.js +52 -21
  116. package/framework/components/chui_media/image_styles.css +3 -3
  117. package/framework/components/chui_media/video.js +155 -62
  118. package/framework/components/chui_media/video_styles.css +33 -16
  119. package/framework/components/chui_menu_bar/menu_bar.js +26 -24
  120. package/framework/components/chui_menu_bar/styles.css +4 -2
  121. package/framework/components/chui_modal/modal.js +44 -43
  122. package/framework/components/chui_modal/styles.css +3 -2
  123. package/framework/components/chui_notification/notification.js +50 -34
  124. package/framework/components/chui_notification/notification_download.js +31 -25
  125. package/framework/components/chui_notification/notification_download_progress.css +3 -2
  126. package/framework/components/chui_notification/notification_download_progress.js +47 -41
  127. package/framework/components/chui_notification/notification_update.js +23 -16
  128. package/framework/components/chui_notification/status_plate.js +2 -2
  129. package/framework/components/chui_notification/styles_download.css +1 -1
  130. package/framework/components/chui_notification/styles_update.css +1 -1
  131. package/framework/components/chui_org_chart/org_chart.js +74 -35
  132. package/framework/components/chui_pagination/pagination.js +62 -27
  133. package/framework/components/chui_paragraph/paragraph.js +2 -2
  134. package/framework/components/chui_paragraph/styles.css +1 -1
  135. package/framework/components/chui_popover/popover.js +49 -28
  136. package/framework/components/chui_popups/popups.js +137 -108
  137. package/framework/components/chui_popups/styles.css +2 -2
  138. package/framework/components/chui_print_button/print_button.js +33 -23
  139. package/framework/components/chui_progress_bar/progress_bar.js +23 -15
  140. package/framework/components/chui_progress_bar/styles.css +2 -2
  141. package/framework/components/chui_progress_ring/progress_ring.js +17 -15
  142. package/framework/components/chui_qrcode/qrcode.js +75 -31
  143. package/framework/components/chui_query_builder/query_builder.js +216 -123
  144. package/framework/components/chui_radio_group/radio_group.js +36 -26
  145. package/framework/components/chui_radio_group/styles.css +1 -1
  146. package/framework/components/chui_result/result.js +26 -16
  147. package/framework/components/chui_scheduler/scheduler.js +57 -35
  148. package/framework/components/chui_scheduler/styles.css +6 -2
  149. package/framework/components/chui_segmented_control/segmented_control.js +27 -21
  150. package/framework/components/chui_settings/settings.js +122 -48
  151. package/framework/components/chui_shortcut_help/shortcut_help.js +55 -32
  152. package/framework/components/chui_sidebar_nav/sidebar_nav.js +33 -23
  153. package/framework/components/chui_skeleton/skeleton.js +29 -24
  154. package/framework/components/chui_slideshow/slideshow.js +60 -41
  155. package/framework/components/chui_slideshow/styles.css +18 -12
  156. package/framework/components/chui_spinner/spinner.js +8 -8
  157. package/framework/components/chui_spinner/styles.css +0 -1
  158. package/framework/components/chui_split_button/split_button.js +23 -18
  159. package/framework/components/chui_split_button/styles.css +1 -1
  160. package/framework/components/chui_splitter/splitter.js +35 -28
  161. package/framework/components/chui_statistic/statistic.js +65 -38
  162. package/framework/components/chui_status_bar/status_bar.js +11 -9
  163. package/framework/components/chui_stepper/stepper.js +27 -22
  164. package/framework/components/chui_sticky_bar/sticky_bar.js +9 -4
  165. package/framework/components/chui_sticky_bar/styles.css +3 -1
  166. package/framework/components/chui_tabs/chui_tabs.js +28 -22
  167. package/framework/components/chui_tabs/styles.css +3 -3
  168. package/framework/components/chui_text_editor/styles.css +20 -19
  169. package/framework/components/chui_text_editor/text_editor.js +85 -61
  170. package/framework/components/chui_text_editor/text_editor_panel.js +346 -284
  171. package/framework/components/chui_theme_toggle/theme_toggle.js +27 -22
  172. package/framework/components/chui_timeline/timeline.js +14 -12
  173. package/framework/components/chui_toast_queue/toast_queue.js +52 -46
  174. package/framework/components/chui_toolbar/toolbar.js +20 -13
  175. package/framework/components/chui_tooltip/tooltip.js +37 -24
  176. package/framework/components/chui_tour/styles.css +8 -3
  177. package/framework/components/chui_tour/tour.js +61 -27
  178. package/framework/components/chui_transfer/styles.css +2 -1
  179. package/framework/components/chui_transfer/transfer.js +75 -42
  180. package/framework/components/chui_tree_table/tree_table.js +43 -29
  181. package/framework/components/chui_tree_view/styles.css +4 -2
  182. package/framework/components/chui_tree_view/tree_view.js +142 -99
  183. package/framework/components/chui_watermark/watermark.js +24 -16
  184. package/framework/components/chui_webview/styles.css +2 -2
  185. package/framework/components/chui_webview/webview.js +62 -50
  186. package/framework/components/telegram_bot/chui_telegram_bot.js +71 -64
  187. package/framework/modules/chui_a11y/chui_a11y.js +77 -46
  188. package/framework/modules/chui_animations/animations.js +42 -42
  189. package/framework/modules/chui_animations/styles.css +1 -1
  190. package/framework/modules/chui_bridge/preload.js +56 -26
  191. package/framework/modules/chui_functions.js +52 -50
  192. package/framework/modules/chui_highlight/chui_highlight.js +25 -22
  193. package/framework/modules/chui_i18n/chui_i18n.js +9 -7
  194. package/framework/modules/chui_i18n/locales/a11y.js +5 -5
  195. package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
  196. package/framework/modules/chui_i18n/locales/calendar.js +3 -3
  197. package/framework/modules/chui_i18n/locales/common.js +3 -3
  198. package/framework/modules/chui_i18n/locales/data.js +36 -26
  199. package/framework/modules/chui_i18n/locales/editors.js +40 -13
  200. package/framework/modules/chui_i18n/locales/feedback.js +3 -3
  201. package/framework/modules/chui_i18n/locales/forms.js +8 -8
  202. package/framework/modules/chui_i18n/locales/graphics.js +8 -8
  203. package/framework/modules/chui_i18n/locales/media.js +4 -4
  204. package/framework/modules/chui_i18n/locales/navigation.js +8 -8
  205. package/framework/modules/chui_i18n/locales/validate.js +3 -3
  206. package/framework/modules/chui_json/chui_json.js +9 -9
  207. package/framework/modules/chui_layout/chui_layout.js +12 -10
  208. package/framework/modules/chui_logger/chui_logger.js +29 -25
  209. package/framework/modules/chui_page/page.js +33 -27
  210. package/framework/modules/chui_page/styles.css +3 -26
  211. package/framework/modules/chui_qr/chui_qr.js +158 -67
  212. package/framework/modules/chui_scrollbar/chui_scrollbar.js +35 -24
  213. package/framework/modules/chui_scrollbar/styles.css +3 -1
  214. package/framework/modules/chui_services/chui_channels.js +76 -11
  215. package/framework/modules/chui_services/chui_services.js +39 -31
  216. package/framework/modules/chui_services/desktop.js +149 -30
  217. package/framework/modules/chui_sortable/chui_sortable.js +17 -12
  218. package/framework/modules/chui_validate/chui_validate.js +7 -4
  219. package/framework/tray_bar/chui_menu_items.js +136 -46
  220. package/index.d.ts +4814 -0
  221. package/index.js +449 -334
  222. package/package.json +79 -68
@@ -1,24 +1,24 @@
1
- const {Animation} = require('../../modules/chui_animations/animations');
2
- const {Icon, Icons} = require('../../components/chui_icons/icons');
3
- const {Button} = require("../../components/chui_button/button");
1
+ const { Animation } = require("../../modules/chui_animations/animations");
2
+ const { Icon, Icons } = require("../../components/chui_icons/icons");
3
+ const { Button } = require("../../components/chui_button/button");
4
4
  // НАСТРОЙКИ
5
- const {Dialog} = require("../../components/chui_modal/modal");
6
- const {Label} = require("../../components/chui_label/label");
7
- const {ContentBlock} = require("../../components/chui_content_block/content_block");
8
- const {Popup} = require("../../components/chui_popups/popups");
9
- const {UpdateNotification} = require("../../components/chui_notification/notification_update");
10
- const {ipcRenderer} = require("electron");
11
- const chui_functions = require('../../modules/chui_functions');
12
- const {DownloadNotification} = require("../../components/chui_notification/notification_download");
13
- const {Log} = require("../../../framework/modules/chui_logger/chui_logger");
14
- const {MenuKeyboard, makeKeyboardButton} = require('../../modules/chui_a11y/chui_a11y');
15
- const {LAYOUT_ROLE, LayoutRefs, registerLayout, requireLayoutNode} = require('../../modules/chui_layout/chui_layout');
16
- const {BRIDGE_CHANNELS} = require('../../modules/chui_services/chui_channels');
17
- const {OverlayScrollbars} = require('../../modules/chui_scrollbar/chui_scrollbar');
18
- const {MenuBar} = require('../../components/chui_menu_bar/menu_bar');
19
- const {Avatar} = require('../../components/chui_avatar/avatar');
20
- const {EmptyState} = require('../../components/chui_empty_state/empty_state');
21
- const {I18n} = require('../../modules/chui_i18n/chui_i18n');
5
+ const { Dialog } = require("../../components/chui_modal/modal");
6
+ const { Label } = require("../../components/chui_label/label");
7
+ const { ContentBlock } = require("../../components/chui_content_block/content_block");
8
+ const { Popup } = require("../../components/chui_popups/popups");
9
+ const { UpdateNotification } = require("../../components/chui_notification/notification_update");
10
+ const { ipcRenderer } = require("electron");
11
+ const chui_functions = require("../../modules/chui_functions");
12
+ const { DownloadNotification } = require("../../components/chui_notification/notification_download");
13
+ const { Log } = require("../../../framework/modules/chui_logger/chui_logger");
14
+ const { MenuKeyboard, makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
15
+ const { LAYOUT_ROLE, LayoutRefs, registerLayout, requireLayoutNode } = require("../../modules/chui_layout/chui_layout");
16
+ const { BRIDGE_CHANNELS } = require("../../modules/chui_services/chui_channels");
17
+ const { OverlayScrollbars } = require("../../modules/chui_scrollbar/chui_scrollbar");
18
+ const { MenuBar } = require("../../components/chui_menu_bar/menu_bar");
19
+ const { Avatar } = require("../../components/chui_avatar/avatar");
20
+ const { EmptyState } = require("../../components/chui_empty_state/empty_state");
21
+ const { I18n } = require("../../modules/chui_i18n/chui_i18n");
22
22
  const fs = require("fs");
23
23
 
24
24
  // Глобальные обработчики ошибок renderer-процесса вешаются один раз на процесс
@@ -28,15 +28,15 @@ class Events {
28
28
  #route_event = undefined;
29
29
  constructor() {}
30
30
  route(page) {
31
- this.#route_event = new CustomEvent("route_event_"+page.constructor.name, {
31
+ this.#route_event = new CustomEvent("route_event_" + page.constructor.name, {
32
32
  bubbles: true,
33
33
  detail: {
34
34
  class: page.constructor.name,
35
35
  title: page.getTitle(),
36
- page: page
36
+ page: page,
37
37
  },
38
38
  });
39
- document.dispatchEvent(this.#route_event)
39
+ document.dispatchEvent(this.#route_event);
40
40
  }
41
41
  }
42
42
 
@@ -88,28 +88,21 @@ class Route extends Events {
88
88
  let center = requireLayoutNode(LAYOUT_ROLE.CENTER);
89
89
  //
90
90
  setPageName(page.getTitle());
91
- center.innerHTML = '';
92
- const page_node = page.render();
93
- // Заголовок страницы — в содержимом, первым блоком (вид — `page_title` в
94
- // chui_page/styles.css). Текст идёт текстовым узлом, а не разметкой: имя
95
- // страницы приходит от приложения и могло бы принести теги
96
- const title = page.getTitle();
97
- if (title !== undefined && title !== null && String(title) !== "") {
98
- const block = document.createElement("page_title");
99
- const icon = page.getIcon();
100
- if (icon !== undefined && icon !== null) block.innerHTML = String(icon);
101
- block.appendChild(document.createTextNode(String(title)));
102
- page_node.prepend(block);
103
- }
104
- center.appendChild(page_node);
105
- const _page = center.querySelector('page');
91
+ center.innerHTML = "";
92
+ // Содержимое собирает сама страница (`Page#render`), оболочка в него не подмешивает
93
+ // ничего: узел `page_title` (был здесь) дублировал заголовок, который страница
94
+ // рисует сама, а `Page#render` отдаёт один и тот же узел — повторный переход на тот
95
+ // же экземпляр страницы (кнопка «Назад» из `Page#setBackButton`) добавлял второй
96
+ // заголовок. Место названия — шапка (`setPageName`).
97
+ center.appendChild(page.render());
98
+ const _page = center.querySelector("page");
106
99
  new Animation(_page).fadeIn();
107
100
  // Стирания `center.removeAttribute("style")` здесь больше нет: инлайновых
108
101
  // стилей на `center` не ставит ни одна часть оболочки, а стирать чужое
109
102
  // (если появится) — это ловушка, на которой уже терялись `display: flex`
110
103
  applyPageMenuBar(requireLayoutNode(LAYOUT_ROLE.HEADER), page.getMenuBar());
111
104
 
112
- this.route(page)
105
+ this.route(page);
113
106
  }
114
107
  }
115
108
 
@@ -168,18 +161,21 @@ function makeHeaderButtonBox(options = { title: undefined, icon: undefined, reve
168
161
  * подобраны под вид набора; цвет наследуется от кнопки через `currentColor`.
169
162
  */
170
163
  const WINDOW_ICONS = {
171
- minimize: '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M4.5 7.5 L10 12.5 L15.5 7.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
172
- maximize: '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M4.5 12.5 L10 7.5 L15.5 12.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
173
- restore: '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M10 5 L15 10 L10 15 L5 10 Z" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
164
+ minimize:
165
+ '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M4.5 7.5 L10 12.5 L15.5 7.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
166
+ maximize:
167
+ '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M4.5 12.5 L10 7.5 L15.5 12.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
168
+ restore:
169
+ '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M10 5 L15 10 L10 15 L5 10 Z" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
174
170
  close: '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M5 5 L15 15 M15 5 L5 15" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>',
175
171
  };
176
172
 
177
173
  class WindowControls {
178
174
  // Управление кнопками
179
- #box = document.createElement("wc_box")
180
- #close = document.createElement("wc_close")
181
- #maximize = document.createElement("wc_maximize")
182
- #minimize = document.createElement("wc_minimize")
175
+ #box = document.createElement("wc_box");
176
+ #close = document.createElement("wc_close");
177
+ #maximize = document.createElement("wc_maximize");
178
+ #minimize = document.createElement("wc_minimize");
183
179
  // Подписка на нересайзящееся окно: set() вызывается и из конструктора Header, и из
184
180
  // addWC(), поэтому в set() слушатель ipcRenderer накапливался.
185
181
  #resizable_false_event = () => this.#maximize.remove();
@@ -195,15 +191,15 @@ class WindowControls {
195
191
  this.#setButton(this.#minimize, I18n.t("appLayout.window.minimize"), "minimize");
196
192
  // «Развернуть» — шеврон вверх, зеркало «свернуть»: пара читается как одна ось
197
193
  this.#setButton(this.#maximize, I18n.t("appLayout.window.maximize"), "maximize");
198
- this.#close.addEventListener("click", () => this.#windowAction("close"))
199
- this.#minimize.addEventListener("click", () => this.#windowAction("minimize"))
194
+ this.#close.addEventListener("click", () => this.#windowAction("close"));
195
+ this.#minimize.addEventListener("click", () => this.#windowAction("minimize"));
200
196
  // Разворот и восстановление — одно действие: решение принимает окно, а не кнопка
201
- this.#maximize.addEventListener("click", () => this.#windowAction("toggle-maximize"))
197
+ this.#maximize.addEventListener("click", () => this.#windowAction("toggle-maximize"));
202
198
  // Разворот приходит не только от нашей кнопки (двойной клик по полосе, меню окна),
203
199
  // поэтому состояние читаем по факту изменения размера окна
204
- window.addEventListener("resize", this.#sync_maximized_event)
205
- this.#syncMaximized()
206
- ipcRenderer.on("chui_resizable_false", this.#resizable_false_event)
200
+ window.addEventListener("resize", this.#sync_maximized_event);
201
+ this.#syncMaximized();
202
+ ipcRenderer.on("chui_resizable_false", this.#resizable_false_event);
207
203
  }
208
204
  /** Кнопка окна — значок с именем для скринридера, фокусом и активацией с клавиатуры.
209
205
  * Значок рисуется линиями (`WINDOW_ICONS`), размер задаёт CSS (см. global_style.css) */
@@ -254,13 +250,13 @@ class WindowControls {
254
250
  #sync_maximized_event = () => this.#syncMaximized();
255
251
  set(pos_bool = Boolean()) {
256
252
  if (pos_bool) {
257
- this.#box.appendChild(this.#close)
258
- this.#box.appendChild(this.#maximize)
259
- this.#box.appendChild(this.#minimize)
253
+ this.#box.appendChild(this.#close);
254
+ this.#box.appendChild(this.#maximize);
255
+ this.#box.appendChild(this.#minimize);
260
256
  } else {
261
- this.#box.appendChild(this.#minimize)
262
- this.#box.appendChild(this.#maximize)
263
- this.#box.appendChild(this.#close)
257
+ this.#box.appendChild(this.#minimize);
258
+ this.#box.appendChild(this.#maximize);
259
+ this.#box.appendChild(this.#close);
264
260
  }
265
261
  return this.#box;
266
262
  }
@@ -283,7 +279,7 @@ class Header {
283
279
  this.#header_main.appendChild(this.#header_left_box);
284
280
  this.#header_main.appendChild(this.#header_right_box);
285
281
  this.#header_main.appendChild(this.#wc_box.set(false));
286
- this.#header.appendChild(this.#header_main)
282
+ this.#header.appendChild(this.#header_main);
287
283
  this.#observeHeight();
288
284
  }
289
285
  /*
@@ -323,7 +319,8 @@ class Header {
323
319
  this.#header_main.insertBefore(this.#wc_box.set(boolean), this.#header_main.firstChild);
324
320
  }
325
321
  addToLeftBeforeTitle(...components) {
326
- for (let component of components) this.#header_left_box.insertBefore(component, this.#header_left_box.firstChild);
322
+ for (let component of components)
323
+ this.#header_left_box.insertBefore(component, this.#header_left_box.firstChild);
327
324
  }
328
325
  /** Прежнее имя с опечаткой (`Tittle`): оставлено синонимом, чтобы не ломать чужие вызовы */
329
326
  addToLeftBeforeTittle(...components) {
@@ -331,11 +328,12 @@ class Header {
331
328
  }
332
329
  addToLeft(...components) {
333
330
  for (let component of components) {
334
- this.#header_left_box.appendChild(component)
331
+ this.#header_left_box.appendChild(component);
335
332
  }
336
333
  }
337
334
  addToRight(...components) {
338
- for (let component of components) this.#header_right_box.insertBefore(component, this.#header_right_box.firstChild);
335
+ for (let component of components)
336
+ this.#header_right_box.insertBefore(component, this.#header_right_box.firstChild);
339
337
  }
340
338
  removeToRight(...components) {
341
339
  for (let component of components) {
@@ -351,7 +349,7 @@ class Header {
351
349
  }
352
350
 
353
351
  class Center {
354
- #center = document.createElement('center');
352
+ #center = document.createElement("center");
355
353
  constructor(layout) {
356
354
  this.#center.id = "center";
357
355
  layout.set(LAYOUT_ROLE.CENTER, this.#center);
@@ -363,12 +361,14 @@ class Center {
363
361
 
364
362
  class AppMenu {
365
363
  #routeList = [];
366
- #appMenu = document.createElement('app_menu');
364
+ #appMenu = document.createElement("app_menu");
367
365
  #appMenuWidth = 400;
368
366
  #appMenuOffset = 25;
369
- get #appMenuSlide() { return this.#appMenuWidth + this.#appMenuOffset; }
370
- #routeViews = document.createElement('route_views');
371
- #appMenuButton = document.createElement('app_menu_button');
367
+ get #appMenuSlide() {
368
+ return this.#appMenuWidth + this.#appMenuOffset;
369
+ }
370
+ #routeViews = document.createElement("route_views");
371
+ #appMenuButton = document.createElement("app_menu_button");
372
372
  // Шапка панели: плитка со значком и заголовок — панель читается выдвижным ящиком
373
373
  #app_menu_header = document.createElement("app_menu_header");
374
374
  #app_menu_header_icon = document.createElement("app_menu_header_icon");
@@ -386,7 +386,7 @@ class AppMenu {
386
386
  #routeKeyboard = undefined;
387
387
  #header = undefined;
388
388
  #center = undefined;
389
- #page_name = document.createElement('page_name');
389
+ #page_name = document.createElement("page_name");
390
390
  constructor(header, center, layout) {
391
391
  this.#header = header;
392
392
  this.#center = center;
@@ -396,9 +396,9 @@ class AppMenu {
396
396
  this.#appMenu.style.width = `${this.#appMenuWidth}px`;
397
397
  this.#appMenu.style.left = `calc(-${this.#appMenuSlide}px)`;
398
398
  //
399
- this.#appMenuButton.id = "appMenuButton"
399
+ this.#appMenuButton.id = "appMenuButton";
400
400
  // Список пунктов — тот узел, которым управляет кнопка: `aria-controls` ссылается на него
401
- this.#routeViews.id = "chui_app_menu"
401
+ this.#routeViews.id = "chui_app_menu";
402
402
  // Значок — украшение: без подписи имя кнопки складывалось из лигатуры («menu»)
403
403
  this.#appMenuButton.setAttribute("aria-label", I18n.t("appLayout.menu"));
404
404
  this.#appMenuButton.setAttribute("title", I18n.t("appLayout.menu"));
@@ -406,16 +406,16 @@ class AppMenu {
406
406
  this.#appMenuButton.addEventListener("click", () => {
407
407
  if (this.#appMenuButton.classList.contains("app_menu_button_active")) this.#close();
408
408
  else this.#open();
409
- })
410
- header.set().addEventListener('click', (e) => {
409
+ });
410
+ header.set().addEventListener("click", (e) => {
411
411
  if (e.target !== this.#appMenuButton) this.#close();
412
- })
413
- center.addEventListener('click', (e) => {
412
+ });
413
+ center.addEventListener("click", (e) => {
414
414
  if (!this.#appMenu.contains(e.target)) {
415
415
  if (this.#appMenu.style.transform === `translateX(${this.#appMenuSlide}px)`) this.#close();
416
416
  }
417
- })
418
- this.#page_name.id = "page_name"
417
+ });
418
+ this.#page_name.id = "page_name";
419
419
  layout.set(LAYOUT_ROLE.PAGE_NAME, this.#page_name);
420
420
  header.addToLeft(this.#appMenuButton, this.#page_name);
421
421
  this.#app_menu_header_icon.appendChild(new Icon(Icons.NAVIGATION.MENU_OPEN, "16px").set());
@@ -425,18 +425,18 @@ class AppMenu {
425
425
  // Список пунктов назван головкой панели: скринридер читает «Меню», а не безымянный список
426
426
  this.#app_menu_header_title.id = "chui_app_menu_title";
427
427
  this.#routeViews.setAttribute("aria-labelledby", this.#app_menu_header_title.id);
428
- this.#app_menu_header.appendChild(this.#app_menu_header_icon)
429
- this.#app_menu_header.appendChild(this.#app_menu_header_title)
428
+ this.#app_menu_header.appendChild(this.#app_menu_header_icon);
429
+ this.#app_menu_header.appendChild(this.#app_menu_header_title);
430
430
  this.#app_menu_empty.innerText = I18n.t("appLayout.search.empty");
431
431
  // Подсказка лежит внутри списка, но пунктом не является: клавиатура меню её не видит (itemSelector "route")
432
- this.#routeViews.appendChild(this.#app_menu_empty)
433
- this.#appMenu.appendChild(this.#app_menu_header)
434
- this.#appMenu.appendChild(this.#routeViews)
432
+ this.#routeViews.appendChild(this.#app_menu_empty);
433
+ this.#appMenu.appendChild(this.#app_menu_header);
434
+ this.#appMenu.appendChild(this.#routeViews);
435
435
  // Клавиатура и aria меню маршрутов: стрелки по пунктам, Escape закрывает и возвращает фокус на кнопку
436
436
  this.#routeKeyboard = new MenuKeyboard(this.#routeViews, {
437
437
  itemSelector: "route",
438
438
  onEscape: () => this.#close(),
439
- trigger: this.#appMenuButton
439
+ trigger: this.#appMenuButton,
440
440
  });
441
441
  makeKeyboardButton(this.#appMenuButton);
442
442
  // Закрытая панель объявляется закрытой сразу: иначе у кнопки не было бы aria-expanded,
@@ -473,18 +473,18 @@ class AppMenu {
473
473
  enableSearchInput() {
474
474
  if (this.#search_enabled) return;
475
475
  this.#search_enabled = true;
476
- this.#app_menu_search_input.classList.add("app_menu_search_input")
477
- this.#app_menu_search_input.placeholder = I18n.t("appLayout.search.placeholder")
478
- this.#app_menu_search_input.setAttribute("aria-label", I18n.t("appLayout.search.sections"))
476
+ this.#app_menu_search_input.classList.add("app_menu_search_input");
477
+ this.#app_menu_search_input.placeholder = I18n.t("appLayout.search.placeholder");
478
+ this.#app_menu_search_input.setAttribute("aria-label", I18n.t("appLayout.search.sections"));
479
479
  // Подсветку поля даёт :focus-within на поверхности поиска — инлайновых стилей больше нет
480
480
  this.#app_menu_search_input.addEventListener("input", (event) => {
481
- this.#filterRoutes(event.target.value)
482
- })
481
+ this.#filterRoutes(event.target.value);
482
+ });
483
483
  this.#app_menu_search_icon.innerHTML = new Icon(Icons.ACTIONS.SEARCH, "15px").getHTML();
484
- this.#app_menu_search_box.appendChild(this.#app_menu_search_icon)
485
- this.#app_menu_search_box.appendChild(this.#app_menu_search_input)
486
- this.#app_menu_search_main.appendChild(this.#app_menu_search_box)
487
- this.#appMenu.insertBefore(this.#app_menu_search_main, this.#routeViews)
484
+ this.#app_menu_search_box.appendChild(this.#app_menu_search_icon);
485
+ this.#app_menu_search_box.appendChild(this.#app_menu_search_input);
486
+ this.#app_menu_search_main.appendChild(this.#app_menu_search_box);
487
+ this.#appMenu.insertBefore(this.#app_menu_search_main, this.#routeViews);
488
488
  }
489
489
  // Пункты списка: подсказка о пустом результате лежит там же, поэтому фильтруем по тегу
490
490
  #routes() {
@@ -506,9 +506,9 @@ class AppMenu {
506
506
  let center = this.#center;
507
507
  //
508
508
  setPageName(page.getTitle());
509
- center.innerHTML = '';
509
+ center.innerHTML = "";
510
510
  center.appendChild(page.render());
511
- const _page = center.querySelector('page');
511
+ const _page = center.querySelector("page");
512
512
  new Animation(_page).fadeIn();
513
513
  // Стирания `center.removeAttribute("style")` здесь больше нет: инлайновых
514
514
  // стилей на `center` не ставит ни одна часть оболочки, а стирать чужое
@@ -517,30 +517,30 @@ class AppMenu {
517
517
  }
518
518
  setRouteTest(page) {
519
519
  this.#routeList.push(page);
520
- let test = this.#routeList.filter(route => route.getTitle().includes(page.getTitle()));
520
+ let test = this.#routeList.filter((route) => route.getTitle().includes(page.getTitle()));
521
521
  if (test.length === 1) {
522
- let button_route = document.createElement('route');
523
- let icon_menu = document.createElement('route_icon');
522
+ let button_route = document.createElement("route");
523
+ let icon_menu = document.createElement("route_icon");
524
524
  icon_menu.innerHTML = page.getIcon();
525
- let title_menu = document.createElement('route_title');
525
+ let title_menu = document.createElement("route_title");
526
526
  title_menu.innerHTML = page.getTitle();
527
527
  if (page.getMain()) {
528
528
  this.go(page);
529
529
  button_route.classList.add("route_active");
530
530
  }
531
- button_route.addEventListener('click', () => {
532
- const eventAwesome = new CustomEvent("route_event_"+page.constructor.name, {
531
+ button_route.addEventListener("click", () => {
532
+ const eventAwesome = new CustomEvent("route_event_" + page.constructor.name, {
533
533
  bubbles: true,
534
534
  detail: {
535
535
  class: page.constructor.name,
536
536
  title: page.getTitle(),
537
- page: page
537
+ page: page,
538
538
  },
539
539
  });
540
- document.dispatchEvent(eventAwesome)
540
+ document.dispatchEvent(eventAwesome);
541
541
 
542
- if (!button_route.classList.contains('route_active')) {
543
- for (const act of this.#routes()) act.classList.remove('route_active');
542
+ if (!button_route.classList.contains("route_active")) {
543
+ for (const act of this.#routes()) act.classList.remove("route_active");
544
544
  this.go(page);
545
545
  button_route.classList.add("route_active");
546
546
  if (this.#auto_close) {
@@ -551,15 +551,15 @@ class AppMenu {
551
551
  }
552
552
  });
553
553
  if (page.getIcon() !== undefined) {
554
- button_route.appendChild(icon_menu)
554
+ button_route.appendChild(icon_menu);
555
555
  }
556
- button_route.appendChild(title_menu)
557
- this.#routeViews.appendChild(button_route)
556
+ button_route.appendChild(title_menu);
557
+ this.#routeViews.appendChild(button_route);
558
558
  this.#routeKeyboard.refresh();
559
559
  // Новый пункт мог не пройти уже введённый фильтр — список и подсказка пересобираются
560
560
  this.#filterRoutes(this.#app_menu_search_input.value);
561
561
  } else {
562
- Log.error(I18n.t("appLayout.search.alreadyAdded", {title: page.getTitle()}))
562
+ Log.error(I18n.t("appLayout.search.alreadyAdded", { title: page.getTitle() }));
563
563
  }
564
564
  }
565
565
  setAutoClose() {
@@ -571,7 +571,7 @@ class AppMenu {
571
571
  }
572
572
 
573
573
  class NotificationBox {
574
- #notification_box = document.createElement('notification_box');
574
+ #notification_box = document.createElement("notification_box");
575
575
  #notification_box_main = document.createElement("notification_box_main");
576
576
  // Шапка ящика: заголовок со счётчиком истории и действие «Очистить»
577
577
  #notification_box_header = document.createElement("notification_box_header");
@@ -585,16 +585,16 @@ class NotificationBox {
585
585
  #observer = undefined;
586
586
  #notification_box_width = 400;
587
587
  #notification_box_width_test = 425;
588
- #notification_button = document.createElement('notification_button');
588
+ #notification_button = document.createElement("notification_button");
589
589
  constructor(header, center, layout) {
590
590
  layout.set(LAYOUT_ROLE.NOTIFICATION_BOX, this.#notification_box);
591
591
  layout.set(LAYOUT_ROLE.NOTIFICATION_BOX_MAIN, this.#notification_box_main);
592
- this.#notification_box_main.id = 'chui_notification_box';
592
+ this.#notification_box_main.id = "chui_notification_box";
593
593
  // Шапка: заголовок, счётчик записей и очистка списка
594
594
  this.#notification_box_title.innerText = I18n.t("appLayout.notifications.title");
595
595
  this.#notification_box_count.setAttribute("aria-hidden", "true");
596
- this.#notification_box_header_text.appendChild(this.#notification_box_title)
597
- this.#notification_box_header_text.appendChild(this.#notification_box_count)
596
+ this.#notification_box_header_text.appendChild(this.#notification_box_title);
597
+ this.#notification_box_header_text.appendChild(this.#notification_box_count);
598
598
  // Очистка — обычная кнопка-действие, а не Button: в шапке нужна тихая ссылка-действие
599
599
  this.#clear_button.className = "notification_box_clear";
600
600
  this.#clear_button.type = "button";
@@ -603,22 +603,22 @@ class NotificationBox {
603
603
  this.#clear_button.appendChild(new Icon(Icons.ACTIONS.DELETE, "14px").set());
604
604
  const clear_title = document.createElement("span");
605
605
  clear_title.innerText = I18n.t("appLayout.notifications.clear");
606
- this.#clear_button.appendChild(clear_title)
606
+ this.#clear_button.appendChild(clear_title);
607
607
  this.#clear_button.addEventListener("click", () => this.#clearAll());
608
- this.#notification_box_header.appendChild(this.#notification_box_header_text)
609
- this.#notification_box_header.appendChild(this.#clear_button)
608
+ this.#notification_box_header.appendChild(this.#notification_box_header_text);
609
+ this.#notification_box_header.appendChild(this.#clear_button);
610
610
  // Пустое состояние — общий компонент, а не своя разметка из значка и подписи: это был
611
611
  // третий вариант одного и того же блока (здесь, в поиске меню, в таблицах). Заодно он
612
612
  // объявляется скринридеру (role="status"), а прежняя заглушка молчала
613
613
  this.#empty_state = new EmptyState({
614
614
  icon: Icons.SOCIAL.NOTIFICATIONS_OFF,
615
615
  title: I18n.t("appLayout.notifications.empty"),
616
- compact: true
616
+ compact: true,
617
617
  });
618
- this.#notification_box_empty.appendChild(this.#empty_state.set())
619
- this.#notification_box_main.appendChild(this.#notification_box_empty)
620
- this.#notification_box.appendChild(this.#notification_box_header)
621
- this.#notification_box.appendChild(this.#notification_box_main)
618
+ this.#notification_box_empty.appendChild(this.#empty_state.set());
619
+ this.#notification_box_main.appendChild(this.#notification_box_empty);
620
+ this.#notification_box.appendChild(this.#notification_box_header);
621
+ this.#notification_box.appendChild(this.#notification_box_main);
622
622
  // Карточки кладёт снаружи Notification, поэтому счётчик, группы и пустое состояние
623
623
  // следим по списку. Разделители дней ящик вставляет сам: на них реакция не нужна,
624
624
  // иначе пересборка дёргала бы сама себя без конца
@@ -630,7 +630,7 @@ class NotificationBox {
630
630
  this.#notification_button.addEventListener("click", () => {
631
631
  if (this.#notification_button.classList.contains("notification_button_active")) this.#closePanel();
632
632
  else this.#openPanel();
633
- })
633
+ });
634
634
  // Значок — украшение: без подписи имя кнопки складывалось из лигатуры («notifications»)
635
635
  const notification_glyph = new Icon(Icons.SOCIAL.NOTIFICATIONS, "var(--header_icon_size)").set();
636
636
  notification_glyph.setAttribute("aria-hidden", "true");
@@ -656,20 +656,21 @@ class NotificationBox {
656
656
  event.preventDefault();
657
657
  this.#closePanel();
658
658
  });
659
- header.set().addEventListener('click', (e) => {
659
+ header.set().addEventListener("click", (e) => {
660
660
  if (e.target !== this.#notification_button) this.#closePanel();
661
- })
662
- center.addEventListener('click', (e) => {
661
+ });
662
+ center.addEventListener("click", (e) => {
663
663
  if (!this.#notification_box.contains(e.target)) {
664
- if (this.#notification_box.style.transform === `translateX(-${this.#notification_box_width_test}px)`) this.#closePanel();
664
+ if (this.#notification_box.style.transform === `translateX(-${this.#notification_box_width_test}px)`)
665
+ this.#closePanel();
665
666
  }
666
- })
667
+ });
667
668
  center.onscroll = () => {
668
669
  // Прокрутка меняет материал шапки: плотнее фон и тень. Класс, а не инлайновые стили:
669
670
  // вид полосы целиком описан в global_style.css
670
671
  header.set().classList.toggle("header_scrolled", center.scrollTop > 15);
671
672
  };
672
- header.addToRight(this.#notification_button)
673
+ header.addToRight(this.#notification_button);
673
674
  }
674
675
  #openPanel() {
675
676
  this.#notification_box.style.transform = `translateX(-${this.#notification_box_width_test}px)`;
@@ -700,7 +701,8 @@ class NotificationBox {
700
701
  */
701
702
  #onBoxMutated(records = []) {
702
703
  const changed = records.some((record) =>
703
- [...record.addedNodes, ...record.removedNodes].some((node) => node.tagName === "NOTIFICATION"));
704
+ [...record.addedNodes, ...record.removedNodes].some((node) => node.tagName === "NOTIFICATION")
705
+ );
704
706
  if (changed) this.#syncState();
705
707
  }
706
708
  // Счётчик, пустое состояние, группы дней и доступность кнопки согласованы с составом ящика
@@ -737,7 +739,8 @@ class NotificationBox {
737
739
  yesterday.setDate(yesterday.getDate() - 1);
738
740
  const group = document.createElement("notification_group");
739
741
  if (day === chui_functions.getDayKey()) group.innerText = I18n.t("appLayout.notifications.today");
740
- else if (day === chui_functions.getDayKey(yesterday)) group.innerText = I18n.t("appLayout.notifications.yesterday");
742
+ else if (day === chui_functions.getDayKey(yesterday))
743
+ group.innerText = I18n.t("appLayout.notifications.yesterday");
741
744
  else {
742
745
  // Ключ хранится как ГГГГ-ММ-ДД, а в подписи порядок привычный: ДД.ММ.ГГГГ
743
746
  const [year, month, date] = String(day).split("-");
@@ -755,9 +758,10 @@ class NotificationBox {
755
758
  #syncUnread(notifications = []) {
756
759
  const unread = notifications.filter((notification) => notification.dataset.unread === "true").length;
757
760
  this.#notification_button.classList.toggle("notification_button_unread", unread > 0);
758
- const label = unread === 0
759
- ? I18n.t("appLayout.notifications.title")
760
- : I18n.t("appLayout.notifications.unreadCount", {count: unread});
761
+ const label =
762
+ unread === 0
763
+ ? I18n.t("appLayout.notifications.title")
764
+ : I18n.t("appLayout.notifications.unreadCount", { count: unread });
761
765
  this.#notification_button.setAttribute("aria-label", label);
762
766
  this.#notification_button.setAttribute("title", label);
763
767
  }
@@ -767,9 +771,9 @@ class NotificationBox {
767
771
  }
768
772
  #clearAll() {
769
773
  for (const notification of this.#notifications()) {
770
- notification.style.transform = "translateX(100%)"
771
- notification.style.opacity = "0"
772
- setTimeout(() => notification.remove(), 300)
774
+ notification.style.transform = "translateX(100%)";
775
+ notification.style.opacity = "0";
776
+ setTimeout(() => notification.remove(), 300);
773
777
  }
774
778
  }
775
779
  getBox() {
@@ -783,7 +787,7 @@ class AppLayout {
783
787
  #center = new Center(this.#layout).set();
784
788
  #appMenu = new AppMenu(this.#header, this.#center, this.#layout);
785
789
  #notificationBox = new NotificationBox(this.#header, this.#center, this.#layout);
786
- #notificationPanel = document.createElement('notification_panel');
790
+ #notificationPanel = document.createElement("notification_panel");
787
791
  // Текущие уведомления: ipc-обработчики регистрируются один раз и работают с последним
788
792
  #downloadNotification = undefined;
789
793
  #updateNotificationLoad = undefined;
@@ -792,18 +796,22 @@ class AppLayout {
792
796
  // Слушатели живут весь процесс — обработчик ошибок обязан быть на месте всегда.
793
797
  #onWindowError = (event) => {
794
798
  const place = event.filename === undefined ? "" : ` (${event.filename}:${event.lineno})`;
795
- this.#logError(I18n.t("appLayout.error.uncaught"), event.error === undefined ? event.message : event.error, place)
796
- }
799
+ this.#logError(
800
+ I18n.t("appLayout.error.uncaught"),
801
+ event.error === undefined ? event.message : event.error,
802
+ place
803
+ );
804
+ };
797
805
  #onUnhandledRejection = (event) => {
798
- this.#logError(I18n.t("appLayout.error.unhandledRejection"), event.reason)
799
- }
806
+ this.#logError(I18n.t("appLayout.error.unhandledRejection"), event.reason);
807
+ };
800
808
  #logError(prefix, value, place = "") {
801
809
  const text = `${prefix}: ${AppLayout.#formatError(value)}${place}`;
802
810
  try {
803
- Log.error(text)
811
+ Log.error(text);
804
812
  } catch (e) {
805
813
  // Сам обработчик ошибок падать не должен (например, ipcRenderer уже недоступен при закрытии окна)
806
- console.error(text, e)
814
+ console.error(text, e);
807
815
  }
808
816
  }
809
817
  static #formatError(value) {
@@ -812,10 +820,10 @@ class AppLayout {
812
820
  return String(value);
813
821
  }
814
822
  constructor() {
815
- require('../../modules/chui_fonts').install();
823
+ require("../../modules/chui_fonts").install();
816
824
  // Глобальные стили
817
- chui_functions.setStyles(__dirname + `/global_style.css`, 'chUiJS_Global_App')
818
- chui_functions.setStyles(__dirname + `/main_theme_style.css`, 'chUiJS_Main_Theme')
825
+ chui_functions.setStyles(__dirname + `/global_style.css`, "chUiJS_Global_App");
826
+ chui_functions.setStyles(__dirname + `/main_theme_style.css`, "chUiJS_Main_Theme");
819
827
  // Необработанные ошибки и reject’ы renderer-процесса — один раз на процесс
820
828
  if (app_error_handlers_installed === false) {
821
829
  app_error_handlers_installed = true;
@@ -825,11 +833,11 @@ class AppLayout {
825
833
  //if (process.platform !== "darwin") document.body.style.border = "1px solid var(--border_color)";
826
834
  // ===
827
835
 
828
- document.body.appendChild(this.#header.set())
829
- document.body.appendChild(this.#center)
830
- document.body.appendChild(this.#appMenu.getMenu())
831
- document.body.appendChild(this.#notificationBox.getBox())
832
- document.body.appendChild(this.#notificationPanel)
836
+ document.body.appendChild(this.#header.set());
837
+ document.body.appendChild(this.#center);
838
+ document.body.appendChild(this.#appMenu.getMenu());
839
+ document.body.appendChild(this.#notificationBox.getBox());
840
+ document.body.appendChild(this.#notificationPanel);
833
841
  // Части layout'а зарегистрированы: уведомления и Route ищут свои узлы по ролям, а не по id
834
842
  this.#layout.set(LAYOUT_ROLE.NOTIFICATION_PANEL, this.#notificationPanel);
835
843
  registerLayout(this.#layout);
@@ -844,41 +852,43 @@ class AppLayout {
844
852
  ipcRenderer.on("sendNotificationDownload", (e, text, body) => {
845
853
  this.#downloadNotification = new DownloadNotification({ title: text, text: body });
846
854
  this.#downloadNotification.show();
847
- })
855
+ });
848
856
  ipcRenderer.on("sendNotificationDownloadUpdate", (e, title, text) => {
849
857
  if (this.#downloadNotification !== undefined) this.#downloadNotification.update(title, text);
850
- })
858
+ });
851
859
  ipcRenderer.on("sendNotificationDownloadComplete", () => {
852
860
  if (this.#downloadNotification !== undefined) this.#downloadNotification.done();
853
- })
861
+ });
854
862
  ipcRenderer.on("sendNotificationDownloadError", () => {
855
863
  if (this.#downloadNotification !== undefined) this.#downloadNotification.error();
856
- })
864
+ });
857
865
  //
858
866
  ipcRenderer.on("sendNotificationUpdateLoad", (e, text, body) => {
859
867
  this.#updateNotificationLoad = new UpdateNotification({ title: text, text: body, spinner: true });
860
868
  this.#updateNotificationLoad.show(true);
861
- })
869
+ });
862
870
  ipcRenderer.on("sendNotificationUpdateLoadClose", () => {
863
871
  if (this.#updateNotificationLoad !== undefined) this.#updateNotificationLoad.hide();
864
- })
872
+ });
865
873
  ipcRenderer.on("sendNotificationUpdate", (e, text, body) => {
866
874
  this.#updateNotification = new UpdateNotification({ title: text, text: body, spinner: false });
867
875
  this.#updateNotification.show(true);
868
- })
876
+ });
869
877
  ipcRenderer.on("sendNotificationUpdateClose", () => {
870
878
  if (this.#updateNotification !== undefined) this.#updateNotification.hide();
871
- })
879
+ });
872
880
  let popup = new Popup();
873
881
  ipcRenderer.once("checkUpdatesTrue", async (e, check, version) => {
874
882
  if (check) {
875
883
  let confirm_res = await popup.confirm({
876
- title: I18n.t("appLayout.update.available", {version: version}), message: I18n.t("appLayout.update.question"),
877
- cancelText: I18n.t("appLayout.update.cancel"), okText: I18n.t("appLayout.update.install")
884
+ title: I18n.t("appLayout.update.available", { version: version }),
885
+ message: I18n.t("appLayout.update.question"),
886
+ cancelText: I18n.t("appLayout.update.cancel"),
887
+ okText: I18n.t("appLayout.update.install"),
878
888
  });
879
- e.sender.send("updateInstallConfirm", confirm_res)
889
+ e.sender.send("updateInstallConfirm", confirm_res);
880
890
  }
881
- })
891
+ });
882
892
  }
883
893
  setWindowControlsLeft() {
884
894
  this.#header.addWC(true);
@@ -905,64 +915,84 @@ class AppLayout {
905
915
  this.#header.addToLeftBeforeTitle(...headerLeft);
906
916
  }
907
917
  disableAppMenu() {
908
- this.#layout.get(LAYOUT_ROLE.APP_MENU_BUTTON).remove()
918
+ this.#layout.get(LAYOUT_ROLE.APP_MENU_BUTTON).remove();
909
919
  // Отступ имени страницы считает CSS: `.page_name_no_menu` в global_style.css
910
- this.#layout.get(LAYOUT_ROLE.PAGE_NAME).classList.add("page_name_no_menu")
920
+ this.#layout.get(LAYOUT_ROLE.PAGE_NAME).classList.add("page_name_no_menu");
911
921
  }
912
922
  setScript(pathToJS = String(), id = String()) {
913
- let data = fs.readFileSync(pathToJS, 'utf8');
914
- let script = document.createElement('script');
923
+ let data = fs.readFileSync(pathToJS, "utf8");
924
+ let script = document.createElement("script");
915
925
  script.innerHTML = String(data);
916
- script.setAttribute('id', id);
926
+ script.setAttribute("id", id);
917
927
  // Скрипт подключается в сам документ (не узел компонента), поэтому проверка по id и document
918
928
  if (document.querySelector(`script[id="${id}"]`) === null) document.body.appendChild(script);
919
929
  }
920
- static TABS(options = {
921
- width: String(),
922
- default: Number(),
923
- tabs: []
924
- }) { return new HeaderTabs(options).set(); }
925
- static BUTTON(options = {
926
- title: String(),
927
- icon: undefined,
928
- reverse: Boolean(),
929
- clickEvent: () => {}
930
- }) { return new HeaderButton(options).set(); }
931
- static USER_PROFILE(options = {
932
- username: String(),
933
- image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
934
- items: []
935
- }) { return new UserProfile(options).set(); }
936
- static USER_PROFILE_ITEM(options = {
937
- title: String(),
938
- icon: undefined,
939
- clickEvent: () => {}
940
- }) { return new UserDDItem(options).set(); }
941
- static DIALOG(options = {
942
- title: String(),
943
- icon: undefined,
944
- reverse: Boolean(),
945
- label: String(),
946
- dialogOptions: {
930
+ static TABS(
931
+ options = {
932
+ width: String(),
933
+ default: Number(),
934
+ tabs: [],
935
+ }
936
+ ) {
937
+ return new HeaderTabs(options).set();
938
+ }
939
+ static BUTTON(
940
+ options = {
941
+ title: String(),
942
+ icon: undefined,
943
+ reverse: Boolean(),
944
+ clickEvent: () => {},
945
+ }
946
+ ) {
947
+ return new HeaderButton(options).set();
948
+ }
949
+ static USER_PROFILE(
950
+ options = {
951
+ username: String(),
952
+ image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
953
+ items: [],
954
+ }
955
+ ) {
956
+ return new UserProfile(options).set();
957
+ }
958
+ static USER_PROFILE_ITEM(
959
+ options = {
947
960
  title: String(),
948
- closeOutSideClick: Boolean(),
949
- style: {
950
- width: String(),
951
- height: String(),
952
- direction: String(),
953
- wrap: String(),
954
- align: String(),
955
- justify: String(),
961
+ icon: undefined,
962
+ clickEvent: () => {},
963
+ }
964
+ ) {
965
+ return new UserDDItem(options).set();
966
+ }
967
+ static DIALOG(
968
+ options = {
969
+ title: String(),
970
+ icon: undefined,
971
+ reverse: Boolean(),
972
+ label: String(),
973
+ dialogOptions: {
974
+ title: String(),
975
+ closeOutSideClick: Boolean(),
976
+ style: {
977
+ width: String(),
978
+ height: String(),
979
+ direction: String(),
980
+ wrap: String(),
981
+ align: String(),
982
+ justify: String(),
983
+ },
984
+ components: [],
956
985
  },
957
- components: []
958
986
  }
959
- }) { return new HeaderDialog(options).set(); }
987
+ ) {
988
+ return new HeaderDialog(options).set();
989
+ }
960
990
  }
961
991
 
962
992
  class HeaderTabs {
963
993
  #id_list = require("randomstring").generate();
964
994
  #header_tabs_list = undefined;
965
- #header_tabs = document.createElement('header_tabs');
995
+ #header_tabs = document.createElement("header_tabs");
966
996
  #keydown_event = (event) => this.#onKeydown(event);
967
997
  constructor(options = { width: String(), default: Number(), tabs: [] }) {
968
998
  this.#header_tabs_list = options.tabs;
@@ -975,13 +1005,13 @@ class HeaderTabs {
975
1005
  item.setAttribute("role", "tab");
976
1006
  item.setAttribute("tabindex", "-1");
977
1007
  this.#header_tabs.appendChild(item);
978
- item.addEventListener('click', (event) => {
1008
+ item.addEventListener("click", (event) => {
979
1009
  if (event.target.tagName === "HEADER_BUTTON_ICON" || event.target.tagName === "HEADER_BUTTON_TITLE") {
980
- this.#setActive(event.target.parentNode)
1010
+ this.#setActive(event.target.parentNode);
981
1011
  } else {
982
- this.#setActive(event.target)
1012
+ this.#setActive(event.target);
983
1013
  }
984
- })
1014
+ });
985
1015
  }
986
1016
  // Клик по выбранной вкладке не переигрывает её действие — как у пункта меню текущего
987
1017
  // раздела (`route_active`, который тоже не переходит повторно). Раньше клик до вкладки
@@ -989,11 +1019,15 @@ class HeaderTabs {
989
1019
  // кликом он снимал у неё и курсор-указатель, и ховер, которых у соседних кнопок шапки
990
1020
  // нет. Слушатель ловит клик в фазе перехвата: действие вкладки и `#setActive` висят
991
1021
  // на самом элементе и иначе сработали бы первыми
992
- this.#header_tabs.addEventListener("click", (event) => {
993
- const tab = event.target.closest === undefined ? null : event.target.closest("header_button");
994
- if (tab === null || tab.getAttribute("active") === null) return;
995
- event.stopImmediatePropagation();
996
- }, true);
1022
+ this.#header_tabs.addEventListener(
1023
+ "click",
1024
+ (event) => {
1025
+ const tab = event.target.closest === undefined ? null : event.target.closest("header_button");
1026
+ if (tab === null || tab.getAttribute("active") === null) return;
1027
+ event.stopImmediatePropagation();
1028
+ },
1029
+ true
1030
+ );
997
1031
  // Пока ни одна вкладка не выбрана, остановкой обхода становится первая по счёту:
998
1032
  // иначе в полосу нельзя было бы войти с клавиатуры вообще
999
1033
  const default_tab = this.#header_tabs_list[options.default === undefined ? 0 : options.default];
@@ -1003,9 +1037,9 @@ class HeaderTabs {
1003
1037
  for (let item of this.#header_tabs_list) {
1004
1038
  if (this.#header_tabs_list.indexOf(item) === options.default) {
1005
1039
  setTimeout(() => {
1006
- item.click()
1040
+ item.click();
1007
1041
  //this.#setActive(item)
1008
- }, 250)
1042
+ }, 250);
1009
1043
  }
1010
1044
  }
1011
1045
  }
@@ -1016,13 +1050,13 @@ class HeaderTabs {
1016
1050
  if (target.getAttribute("active") === null) {
1017
1051
  // `children`, а не `childNodes`: в `childNodes` попадают и текстовые узлы, у которых нет classList.
1018
1052
  // `children` — HTMLCollection, поэтому перебираем копию
1019
- [...this.#header_tabs.children].forEach(child => {
1053
+ [...this.#header_tabs.children].forEach((child) => {
1020
1054
  child.removeAttribute("active");
1021
1055
  child.removeAttribute("aria-current");
1022
1056
  child.removeAttribute("aria-selected");
1023
1057
  child.setAttribute("tabindex", "-1");
1024
- child.classList.remove("header_tab_active")
1025
- })
1058
+ child.classList.remove("header_tab_active");
1059
+ });
1026
1060
  target.setAttribute("active", true);
1027
1061
  // Вкладка без `active` была только подсвечена классом: скринридер о выборе не знал (как у пункта меню маршрутов)
1028
1062
  target.setAttribute("aria-current", "page");
@@ -1030,7 +1064,7 @@ class HeaderTabs {
1030
1064
  target.setAttribute("aria-selected", "true");
1031
1065
  // Выбранная вкладка — единственная остановка Tab в полосе вкладок
1032
1066
  target.setAttribute("tabindex", "0");
1033
- target.classList.add("header_tab_active")
1067
+ target.classList.add("header_tab_active");
1034
1068
  }
1035
1069
  }
1036
1070
  /*
@@ -1047,7 +1081,7 @@ class HeaderTabs {
1047
1081
  if (tabs.length === 0) return;
1048
1082
  const focused = tabs.indexOf(document.activeElement);
1049
1083
  // Фокус мог быть не в полосе: тогда считаем от вкладки с остановкой обхода
1050
- const from = focused === -1 ? tabs.findIndex(tab => tab.getAttribute("tabindex") === "0") : focused;
1084
+ const from = focused === -1 ? tabs.findIndex((tab) => tab.getAttribute("tabindex") === "0") : focused;
1051
1085
  const start = from === -1 ? 0 : from;
1052
1086
  const last = tabs.length - 1;
1053
1087
  let next;
@@ -1082,23 +1116,25 @@ class UserProfile {
1082
1116
  #window_click_event = (event) => {
1083
1117
  if (this.#user_main.contains(event.target) || this.#user_dropdown.contains(event.target)) return;
1084
1118
  this.#closeMenu();
1085
- }
1119
+ };
1086
1120
  // Выбор пункта закрывает меню. Обработчик на списке, а не на каждом пункте: пункты
1087
1121
  // приходят из UserDDItem и могут быть добавлены кем угодно снаружи
1088
1122
  #dropdown_click_event = (event) => {
1089
1123
  const item = event.target.closest === undefined ? null : event.target.closest("user_item");
1090
1124
  if (item === null || !this.#user_dropdown.contains(item)) return;
1091
1125
  this.#closeMenu();
1092
- }
1126
+ };
1093
1127
  //
1094
- constructor(options = {
1095
- username: String(),
1096
- image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
1097
- items: []
1098
- }) {
1128
+ constructor(
1129
+ options = {
1130
+ username: String(),
1131
+ image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
1132
+ items: [],
1133
+ }
1134
+ ) {
1099
1135
  this.#username = options.username;
1100
1136
  this.#user_button.innerText = options.username;
1101
- this.#user_main.appendChild(this.#user_button)
1137
+ this.#user_main.appendChild(this.#user_button);
1102
1138
  UserProfile.#instances.set(this.#user_main, this);
1103
1139
  // Панель — слой в body: внутри шапки (у неё свой backdrop-filter) её blur не работал бы,
1104
1140
  // а выдвижные слои оболочки перекрывали бы её. Идентификатор нужен для aria-controls
@@ -1107,7 +1143,7 @@ class UserProfile {
1107
1143
  this.#user_button.addEventListener("click", () => {
1108
1144
  if (this.#is_open) this.#closeMenu();
1109
1145
  else this.#openMenu();
1110
- })
1146
+ });
1111
1147
  //
1112
1148
  if (options.image !== undefined) {
1113
1149
  // Имя пользователя уже есть в кнопке и в пунктах меню, поэтому аватар — украшение
@@ -1115,7 +1151,7 @@ class UserProfile {
1115
1151
  name: options.username,
1116
1152
  image: options.image,
1117
1153
  size: "80px",
1118
- decorative: true
1154
+ decorative: true,
1119
1155
  });
1120
1156
  this.#user_dd_image_main.appendChild(this.#avatar.set());
1121
1157
  this.#user_dropdown.appendChild(this.#user_dd_image_main);
@@ -1127,7 +1163,7 @@ class UserProfile {
1127
1163
  this.#menuKeyboard = new MenuKeyboard(this.#user_dropdown, {
1128
1164
  itemSelector: "user_item",
1129
1165
  onEscape: () => this.#closeMenu(),
1130
- trigger: this.#user_button
1166
+ trigger: this.#user_button,
1131
1167
  });
1132
1168
  makeKeyboardButton(this.#user_button);
1133
1169
  // Закрытое меню объявляется закрытым сразу: иначе у кнопки не было бы aria-expanded
@@ -1220,11 +1256,13 @@ class UserProfile {
1220
1256
  class UserDDItem {
1221
1257
  #user_item = document.createElement("user_item");
1222
1258
 
1223
- constructor(options = {
1224
- title: String(),
1225
- icon: undefined,
1226
- clickEvent: () => {}
1227
- }) {
1259
+ constructor(
1260
+ options = {
1261
+ title: String(),
1262
+ icon: undefined,
1263
+ clickEvent: () => {},
1264
+ }
1265
+ ) {
1228
1266
  if (options.title !== undefined && options.icon !== undefined) {
1229
1267
  this.#user_item.innerText = options.title;
1230
1268
  // Значок ведёт строку — как в пунктах context_menu_item и выпадающих списков
@@ -1236,7 +1274,7 @@ class UserDDItem {
1236
1274
  } else if (options.icon !== undefined) {
1237
1275
  this.#user_item.innerHTML = new Icon(options.icon).getHTML();
1238
1276
  }
1239
- this.#user_item.addEventListener("click", options.clickEvent)
1277
+ this.#user_item.addEventListener("click", options.clickEvent);
1240
1278
  }
1241
1279
 
1242
1280
  set() {
@@ -1246,25 +1284,27 @@ class UserDDItem {
1246
1284
 
1247
1285
  class HeaderDialog {
1248
1286
  #header_button = undefined;
1249
- constructor(options = {
1250
- title: String(),
1251
- icon: undefined,
1252
- reverse: Boolean(),
1253
- label: String(),
1254
- dialogOptions: {
1287
+ constructor(
1288
+ options = {
1255
1289
  title: String(),
1256
- closeOutSideClick: Boolean(),
1257
- style: {
1258
- width: String(),
1259
- height: String(),
1260
- direction: String(),
1261
- wrap: String(),
1262
- align: String(),
1263
- justify: String(),
1290
+ icon: undefined,
1291
+ reverse: Boolean(),
1292
+ label: String(),
1293
+ dialogOptions: {
1294
+ title: String(),
1295
+ closeOutSideClick: Boolean(),
1296
+ style: {
1297
+ width: String(),
1298
+ height: String(),
1299
+ direction: String(),
1300
+ wrap: String(),
1301
+ align: String(),
1302
+ justify: String(),
1303
+ },
1304
+ components: [],
1264
1305
  },
1265
- components: []
1266
1306
  }
1267
- }) {
1307
+ ) {
1268
1308
  // Неполные `dialogOptions` сборку не роняют: `style` и `components` — необязательные
1269
1309
  // части опции, а прямое обращение к ним давало TypeError при создании кнопки
1270
1310
  // («Cannot read properties of undefined») ещё до первого открытия слоя
@@ -1272,22 +1312,22 @@ class HeaderDialog {
1272
1312
  const dialog_style = dialog_options.style === undefined ? {} : dialog_options.style;
1273
1313
  const dialog_components = dialog_options.components === undefined ? [] : dialog_options.components;
1274
1314
  // Пустой заголовок остаётся пустым: заготовка опции рисовала бы слово «undefined»
1275
- const dialog_title = dialog_options.title === undefined || dialog_options.title === null
1276
- ? "" : dialog_options.title;
1315
+ const dialog_title =
1316
+ dialog_options.title === undefined || dialog_options.title === null ? "" : dialog_options.title;
1277
1317
  let dialog = new Dialog({
1278
1318
  width: dialog_style.width,
1279
1319
  height: dialog_style.height,
1280
1320
  closeOutSideClick: dialog_options.closeOutSideClick,
1281
1321
  // Имя модального слоя для скринридера: без него диалог объявлялся бы безымянным
1282
- title: dialog_title
1283
- })
1322
+ title: dialog_title,
1323
+ });
1284
1324
  // HEADER
1285
1325
  let header_dialog = new ContentBlock({
1286
1326
  direction: "row",
1287
1327
  wrap: "nowrap",
1288
1328
  align: "center",
1289
1329
  justify: "space-between",
1290
- disableMarginChild: true
1330
+ disableMarginChild: true,
1291
1331
  });
1292
1332
  header_dialog.setWidth("-webkit-fill-available");
1293
1333
  // Заголовок — как у заголовков панелей оболочки (размер `--font_default_size`, вес 500
@@ -1295,13 +1335,13 @@ class HeaderDialog {
1295
1335
  let title = new Label({
1296
1336
  markdownText: `**${dialog_title}**`,
1297
1337
  wordBreak: "normal",
1298
- width: "max-content"
1299
- })
1338
+ width: "max-content",
1339
+ });
1300
1340
  let close_button = new Button({
1301
1341
  icon: Icons.NAVIGATION.CLOSE,
1302
1342
  label: I18n.t("common.close"),
1303
1343
  reverse: true,
1304
- clickEvent: () => dialog.close()
1344
+ clickEvent: () => dialog.close(),
1305
1345
  });
1306
1346
  header_dialog.add(title, close_button);
1307
1347
  dialog.addToHeader(header_dialog);
@@ -1310,10 +1350,10 @@ class HeaderDialog {
1310
1350
  direction: dialog_style.direction,
1311
1351
  wrap: dialog_style.wrap,
1312
1352
  align: dialog_style.align,
1313
- justify: dialog_style.justify
1353
+ justify: dialog_style.justify,
1314
1354
  });
1315
1355
  body_dialog.setWidth("-webkit-fill-available");
1316
- body_dialog.add(...dialog_components)
1356
+ body_dialog.add(...dialog_components);
1317
1357
  dialog.addToBody(body_dialog);
1318
1358
  // FOOTER
1319
1359
 
@@ -1335,14 +1375,15 @@ class HeaderDialog {
1335
1375
  class HeaderButton {
1336
1376
  #header_button = undefined;
1337
1377
 
1338
- constructor(options = {
1339
- title: String(),
1340
- icon: undefined,
1341
- reverse: Boolean(),
1342
- label: String(),
1343
- clickEvent: () => {
1378
+ constructor(
1379
+ options = {
1380
+ title: String(),
1381
+ icon: undefined,
1382
+ reverse: Boolean(),
1383
+ label: String(),
1384
+ clickEvent: () => {},
1344
1385
  }
1345
- }) {
1386
+ ) {
1346
1387
  this.#header_button = makeHeaderButtonBox(options);
1347
1388
  this.#header_button.addEventListener("click", options.clickEvent);
1348
1389
  }
@@ -1352,5 +1393,5 @@ class HeaderButton {
1352
1393
  }
1353
1394
  }
1354
1395
 
1355
- exports.AppLayout = AppLayout
1356
- exports.Route = Route
1396
+ exports.AppLayout = AppLayout;
1397
+ exports.Route = Route;