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
@@ -14,14 +14,14 @@
14
14
  * (popup_button_ok и подобным) tabindex выставляет вызывающий компонент.
15
15
  */
16
16
  const FOCUSABLE_SELECTOR = [
17
- 'a[href]',
18
- 'button:not([disabled])',
19
- 'input:not([disabled])',
20
- 'select:not([disabled])',
21
- 'textarea:not([disabled])',
17
+ "a[href]",
18
+ "button:not([disabled])",
19
+ "input:not([disabled])",
20
+ "select:not([disabled])",
21
+ "textarea:not([disabled])",
22
22
  '[contenteditable="true"]',
23
23
  '[tabindex]:not([tabindex="-1"])',
24
- ].join(', ');
24
+ ].join(", ");
25
25
 
26
26
  class OverlayA11y {
27
27
  // Стек открытых слоёв: нужен, чтобы вложенный слой не снимал фон-инерт у родителя
@@ -37,10 +37,16 @@ class OverlayA11y {
37
37
  #previous_focus = undefined;
38
38
  #is_open = false;
39
39
 
40
- constructor(element, options = {
41
- role: String(), label: String(), labelNode: undefined, describedBy: undefined,
42
- onEscape: undefined,
43
- }) {
40
+ constructor(
41
+ element,
42
+ options = {
43
+ role: String(),
44
+ label: String(),
45
+ labelNode: undefined,
46
+ describedBy: undefined,
47
+ onEscape: undefined,
48
+ }
49
+ ) {
44
50
  this.#element = element;
45
51
  if (options.role !== undefined) this.#role = options.role;
46
52
  this.#label = options.label;
@@ -125,8 +131,9 @@ class OverlayA11y {
125
131
  }
126
132
 
127
133
  #focusables() {
128
- return [...this.#element.querySelectorAll(FOCUSABLE_SELECTOR)]
129
- .filter(element => element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement);
134
+ return [...this.#element.querySelectorAll(FOCUSABLE_SELECTOR)].filter(
135
+ (element) => element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
136
+ );
130
137
  }
131
138
 
132
139
  #focusFirst() {
@@ -175,7 +182,7 @@ class OverlayA11y {
175
182
  return;
176
183
  }
177
184
  if (event.key === "Tab") this.#trapFocus(event);
178
- }
185
+ };
179
186
  }
180
187
 
181
188
  /**
@@ -194,16 +201,22 @@ function makeKeyboardButton(element, role = "button") {
194
201
  return element;
195
202
  }
196
203
 
197
- exports.OverlayA11y = OverlayA11y
198
- exports.makeKeyboardButton = makeKeyboardButton
199
- exports.A11Y_FOCUSABLE_SELECTOR = FOCUSABLE_SELECTOR
204
+ exports.OverlayA11y = OverlayA11y;
205
+ exports.makeKeyboardButton = makeKeyboardButton;
206
+ exports.A11Y_FOCUSABLE_SELECTOR = FOCUSABLE_SELECTOR;
200
207
 
201
208
  // Подсветка активного пункта меню/опции, когда фокус остаётся на поле ввода
202
209
  const ACTIVE_CLASS = "chui_a11y_active";
203
210
 
204
- const isVisible = (element) => element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement;
205
- const isDisabled = (element) => element.disabled === true || element.getAttribute("aria-disabled") === "true" ||
206
- (element.className !== undefined && String(element.className).split(" ").some(name => name.endsWith("_disabled")));
211
+ const isVisible = (element) =>
212
+ element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement;
213
+ const isDisabled = (element) =>
214
+ element.disabled === true ||
215
+ element.getAttribute("aria-disabled") === "true" ||
216
+ (element.className !== undefined &&
217
+ String(element.className)
218
+ .split(" ")
219
+ .some((name) => name.endsWith("_disabled")));
207
220
 
208
221
  /**
209
222
  * Клавиатура для всплывающих меню (ContextMenu, MenuBar, меню шапки).
@@ -225,10 +238,17 @@ class MenuKeyboard {
225
238
  #trigger = undefined;
226
239
  #trigger_bound = false;
227
240
 
228
- constructor(container, options = {
229
- role: String(), itemRole: String(), itemSelector: String(),
230
- submenuSelector: String(), onEscape: undefined, trigger: undefined
231
- }) {
241
+ constructor(
242
+ container,
243
+ options = {
244
+ role: String(),
245
+ itemRole: String(),
246
+ itemSelector: String(),
247
+ submenuSelector: String(),
248
+ onEscape: undefined,
249
+ trigger: undefined,
250
+ }
251
+ ) {
232
252
  this.#container = container;
233
253
  if (options.role !== undefined) this.#role = options.role;
234
254
  if (options.itemRole !== undefined) this.#item_role = options.itemRole;
@@ -276,8 +296,9 @@ class MenuKeyboard {
276
296
 
277
297
  // Все пункты своего уровня (и скрытые — им тоже нужны роли и tabindex)
278
298
  #allItems() {
279
- return [...this.#container.querySelectorAll(this.#item_selector)]
280
- .filter(item => item.closest('[role="menu"]') === this.#container);
299
+ return [...this.#container.querySelectorAll(this.#item_selector)].filter(
300
+ (item) => item.closest('[role="menu"]') === this.#container
301
+ );
281
302
  }
282
303
 
283
304
  #focusItem(index) {
@@ -290,7 +311,7 @@ class MenuKeyboard {
290
311
  }
291
312
 
292
313
  #activeIndex(items) {
293
- return items.findIndex(item => item === document.activeElement || item.classList.contains(ACTIVE_CLASS));
314
+ return items.findIndex((item) => item === document.activeElement || item.classList.contains(ACTIVE_CLASS));
294
315
  }
295
316
 
296
317
  // Для навигации — только видимые пункты своего уровня
@@ -382,7 +403,7 @@ class MenuKeyboard {
382
403
  }
383
404
  break;
384
405
  }
385
- }
406
+ };
386
407
 
387
408
  #typeahead(char) {
388
409
  const now = Date.now();
@@ -394,7 +415,7 @@ class MenuKeyboard {
394
415
  const current = this.#activeIndex(items);
395
416
  // Ищем по кругу, начиная со следующего пункта
396
417
  const ordered = current === -1 ? items : [...items.slice(current + 1), ...items.slice(0, current + 1)];
397
- const match = ordered.find(item => this.#label(item).startsWith(this.#type_buffer));
418
+ const match = ordered.find((item) => this.#label(item).startsWith(this.#type_buffer));
398
419
  if (match !== undefined) this.#focusItem(items.indexOf(match));
399
420
  }
400
421
 
@@ -412,7 +433,8 @@ class MenuKeyboard {
412
433
 
413
434
  destroy() {
414
435
  this.#container.removeEventListener("keydown", this.#keydownEvent);
415
- if (this.#trigger !== undefined && this.#trigger_bound) this.#trigger.removeEventListener("keydown", this.#keydownEvent);
436
+ if (this.#trigger !== undefined && this.#trigger_bound)
437
+ this.#trigger.removeEventListener("keydown", this.#keydownEvent);
416
438
  }
417
439
  }
418
440
 
@@ -430,9 +452,16 @@ class ComboboxKeyboard {
430
452
  #active = undefined;
431
453
  #selection_state = undefined;
432
454
 
433
- constructor(input, options = {
434
- dropdown: undefined, optionSelector: String(), onEscape: undefined, onOpen: undefined, selectionState: undefined
435
- }) {
455
+ constructor(
456
+ input,
457
+ options = {
458
+ dropdown: undefined,
459
+ optionSelector: String(),
460
+ onEscape: undefined,
461
+ onOpen: undefined,
462
+ selectionState: undefined,
463
+ }
464
+ ) {
436
465
  this.#input = input;
437
466
  this.#dropdown = options.dropdown;
438
467
  this.#option_selector = options.optionSelector;
@@ -455,7 +484,8 @@ class ComboboxKeyboard {
455
484
  // Роли и id нужны всем опциям, даже когда дропдаун ещё закрыт
456
485
  for (const option of this.#allOptions()) {
457
486
  if (option.getAttribute("role") === null) option.setAttribute("role", "option");
458
- if (option.id === undefined || option.id === "") option.id = "chui_a11y_option_" + Math.random().toString(36).slice(2, 9);
487
+ if (option.id === undefined || option.id === "")
488
+ option.id = "chui_a11y_option_" + Math.random().toString(36).slice(2, 9);
459
489
  }
460
490
  this.#applyActive();
461
491
  }
@@ -477,7 +507,7 @@ class ComboboxKeyboard {
477
507
  }
478
508
 
479
509
  #options() {
480
- return this.#allOptions().filter(option => isVisible(option) && !isDisabled(option));
510
+ return this.#allOptions().filter((option) => isVisible(option) && !isDisabled(option));
481
511
  }
482
512
 
483
513
  #applyActive() {
@@ -511,7 +541,8 @@ class ComboboxKeyboard {
511
541
  case "ArrowDown":
512
542
  case "ArrowUp": {
513
543
  event.preventDefault();
514
- if (this.#input.getAttribute("aria-expanded") !== "true" && this.#on_open !== undefined) this.#on_open();
544
+ if (this.#input.getAttribute("aria-expanded") !== "true" && this.#on_open !== undefined)
545
+ this.#on_open();
515
546
  this.#move(event.key === "ArrowDown" ? 1 : -1);
516
547
  break;
517
548
  }
@@ -547,7 +578,7 @@ class ComboboxKeyboard {
547
578
  default:
548
579
  break;
549
580
  }
550
- }
581
+ };
551
582
 
552
583
  destroy() {
553
584
  this.#input.removeEventListener("keydown", this.#keydownEvent);
@@ -582,7 +613,7 @@ class RovingKeyboard {
582
613
  /** Перечитать пункты: остановка остаётся на прежнем, если он ещё доступен */
583
614
  refresh() {
584
615
  const visible = this.#items();
585
- const current = visible.find(item => item.getAttribute("tabindex") === "0");
616
+ const current = visible.find((item) => item.getAttribute("tabindex") === "0");
586
617
  // Панель может быть ещё не в документе (размеры нулевые) — тогда остановку получает
587
618
  // первый пункт: без неё группа целиком выпала бы из обхода Tab
588
619
  const fallback = visible[0] === undefined ? this.#allItems()[0] : visible[0];
@@ -598,13 +629,13 @@ class RovingKeyboard {
598
629
 
599
630
  // Пункты, по которым ходит клавиатура: видимые и не запрещённые
600
631
  #items() {
601
- return this.#allItems().filter(item => !isDisabled(item) && isVisible(item));
632
+ return this.#allItems().filter((item) => !isDisabled(item) && isVisible(item));
602
633
  }
603
634
 
604
635
  #activeIndex(items) {
605
- const focused = items.findIndex(item => item === document.activeElement);
636
+ const focused = items.findIndex((item) => item === document.activeElement);
606
637
  if (focused !== -1) return focused;
607
- return items.findIndex(item => item.getAttribute("tabindex") === "0");
638
+ return items.findIndex((item) => item.getAttribute("tabindex") === "0");
608
639
  }
609
640
 
610
641
  #applyStop(item) {
@@ -659,10 +690,10 @@ class RovingKeyboard {
659
690
  }
660
691
  }
661
692
 
662
- exports.MenuKeyboard = MenuKeyboard
663
- exports.ComboboxKeyboard = ComboboxKeyboard
664
- exports.RovingKeyboard = RovingKeyboard
665
- exports.A11Y_ACTIVE_CLASS = ACTIVE_CLASS
693
+ exports.MenuKeyboard = MenuKeyboard;
694
+ exports.ComboboxKeyboard = ComboboxKeyboard;
695
+ exports.RovingKeyboard = RovingKeyboard;
696
+ exports.A11Y_ACTIVE_CLASS = ACTIVE_CLASS;
666
697
 
667
698
  /**
668
699
  * Перенести фокус на следующий (или предыдущий) видимый фокусируемый элемент страницы.
@@ -670,7 +701,7 @@ exports.A11Y_ACTIVE_CLASS = ACTIVE_CLASS
670
701
  * браузер уведёт фокус в `body`, а не к следующему полю. `skip` — узел или поддерево,
671
702
  * которое в обходе не участвует (например, сам список).
672
703
  */
673
- function moveFocusAway(from, options = {backwards: false, skip: undefined}) {
704
+ function moveFocusAway(from, options = { backwards: false, skip: undefined }) {
674
705
  const nodes = [...document.querySelectorAll(FOCUSABLE_SELECTOR)]
675
706
  .filter((node) => node.disabled !== true)
676
707
  .filter((node) => isVisible(node))
@@ -684,4 +715,4 @@ function moveFocusAway(from, options = {backwards: false, skip: undefined}) {
684
715
  return true;
685
716
  }
686
717
 
687
- exports.moveFocusAway = moveFocusAway
718
+ exports.moveFocusAway = moveFocusAway;
@@ -4,7 +4,7 @@ class Animation {
4
4
  #element = undefined;
5
5
 
6
6
  constructor(element) {
7
- require('../chui_functions').setStyles(__dirname + "/styles.css", "chUiJS_Animations");
7
+ require("../chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_Animations");
8
8
  if (element !== undefined) this.#element = element;
9
9
  }
10
10
 
@@ -14,101 +14,101 @@ class Animation {
14
14
  * доиграла, этот обработчик прятал уже открытый слой — вид расходился с состоянием.
15
15
  */
16
16
  #cancelHide() {
17
- this.#element.removeEventListener('animationend', removeEvents);
18
- this.#element.style.removeProperty('animation-name');
17
+ this.#element.removeEventListener("animationend", removeEvents);
18
+ this.#element.style.removeProperty("animation-name");
19
19
  }
20
20
 
21
21
  fadeIn() {
22
22
  this.#cancelHide();
23
- if (window.getComputedStyle(this.#element, null).display === 'none') {
24
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
23
+ if (window.getComputedStyle(this.#element, null).display === "none") {
24
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
25
25
  }
26
- this.#element.style.transition = "transform .0s"
27
- this.#element.style.animationName = 'fade-in';
26
+ this.#element.style.transition = "transform .0s";
27
+ this.#element.style.animationName = "fade-in";
28
28
  }
29
29
 
30
30
  fadeOut() {
31
- this.#element.style.transition = "transform .0s"
31
+ this.#element.style.transition = "transform .0s";
32
32
  if (this.#element.style.animationName) {
33
- this.#element.style.animationName = 'fade-out';
34
- this.#element.addEventListener('animationend', removeEvents);
33
+ this.#element.style.animationName = "fade-out";
34
+ this.#element.addEventListener("animationend", removeEvents);
35
35
  }
36
36
  }
37
37
 
38
38
  // SCALE
39
39
  scaleIn() {
40
40
  this.#cancelHide();
41
- if (window.getComputedStyle(this.#element, null).display === 'none') {
42
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
41
+ if (window.getComputedStyle(this.#element, null).display === "none") {
42
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
43
43
  }
44
- this.#element.style.animationName = 'scale-in';
44
+ this.#element.style.animationName = "scale-in";
45
45
  }
46
46
 
47
47
  scaleOut() {
48
48
  if (this.#element.style.animationName) {
49
- this.#element.style.animationName = 'scale-out';
50
- this.#element.addEventListener('animationend', removeEvents);
49
+ this.#element.style.animationName = "scale-out";
50
+ this.#element.addEventListener("animationend", removeEvents);
51
51
  }
52
52
  }
53
53
 
54
54
  // SLIDE RIGHT
55
55
  slideRightIn() {
56
56
  this.#cancelHide();
57
- if (window.getComputedStyle(this.#element, null).display === 'none') {
58
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
57
+ if (window.getComputedStyle(this.#element, null).display === "none") {
58
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
59
59
  }
60
- this.#element.style.animationName = 'slide-right-in';
60
+ this.#element.style.animationName = "slide-right-in";
61
61
  }
62
62
 
63
63
  slideRightOut() {
64
64
  if (this.#element.style.animationName) {
65
- this.#element.style.animationName = 'slide-right-out';
66
- this.#element.addEventListener('animationend', removeEvents);
65
+ this.#element.style.animationName = "slide-right-out";
66
+ this.#element.addEventListener("animationend", removeEvents);
67
67
  }
68
68
  }
69
69
 
70
70
  // SLIDE BOTTOM
71
71
  slideBottomIn() {
72
72
  this.#cancelHide();
73
- if (window.getComputedStyle(this.#element, null).display === 'none') {
74
- if (this.#element.tagName !== "TABLE") this.#element.style.display = 'flex';
73
+ if (window.getComputedStyle(this.#element, null).display === "none") {
74
+ if (this.#element.tagName !== "TABLE") this.#element.style.display = "flex";
75
75
  }
76
- this.#element.style.animationName = 'slide-bottom-in';
76
+ this.#element.style.animationName = "slide-bottom-in";
77
77
  }
78
78
 
79
79
  slideBottomOut() {
80
80
  if (this.#element.style.animationName) {
81
- this.#element.style.animationName = 'slide-bottom-out';
82
- this.#element.addEventListener('animationend', removeEvents);
81
+ this.#element.style.animationName = "slide-bottom-out";
82
+ this.#element.addEventListener("animationend", removeEvents);
83
83
  }
84
84
  }
85
85
 
86
86
  // REMOVE
87
87
  slideBottomOutAndRemove() {
88
88
  if (this.#element.style.animationName) {
89
- this.#element.style.animationName = 'slide-bottom-out';
90
- this.#element.addEventListener('animationend', removeEvents);
89
+ this.#element.style.animationName = "slide-bottom-out";
90
+ this.#element.addEventListener("animationend", removeEvents);
91
91
  }
92
92
  }
93
93
 
94
94
  slideRightOutAndRemove() {
95
95
  if (this.#element.style.animationName) {
96
- this.#element.style.animationName = 'slide-right-out';
97
- this.#element.addEventListener('animationend', removeEvents);
96
+ this.#element.style.animationName = "slide-right-out";
97
+ this.#element.addEventListener("animationend", removeEvents);
98
98
  }
99
99
  }
100
100
 
101
101
  scaleOutAndRemove() {
102
102
  if (this.#element.style.animationName) {
103
- this.#element.style.animationName = 'scale-out';
104
- this.#element.addEventListener('animationend', removeEvents2);
103
+ this.#element.style.animationName = "scale-out";
104
+ this.#element.addEventListener("animationend", removeEvents2);
105
105
  }
106
106
  }
107
107
 
108
108
  fadeOutAndRemove() {
109
109
  if (this.#element.style.animationName) {
110
- this.#element.style.animationName = 'fade-out';
111
- this.#element.addEventListener('animationend', removeEvents2);
110
+ this.#element.style.animationName = "fade-out";
111
+ this.#element.addEventListener("animationend", removeEvents2);
112
112
  }
113
113
  }
114
114
 
@@ -118,19 +118,19 @@ class Animation {
118
118
  }
119
119
 
120
120
  removeStyles() {
121
- this.#element.removeAttribute("style")
121
+ this.#element.removeAttribute("style");
122
122
  }
123
123
  }
124
124
 
125
125
  const removeEvents2 = (event) => {
126
- event.target.remove()
127
- event.target.removeEventListener('animationend', removeEvents2);
128
- }
126
+ event.target.remove();
127
+ event.target.removeEventListener("animationend", removeEvents2);
128
+ };
129
129
 
130
130
  const removeEvents = (event) => {
131
- event.target.style.removeProperty('animation-name');
132
- event.target.style.removeProperty('display');
133
- event.target.removeEventListener('animationend', removeEvents);
134
- }
131
+ event.target.style.removeProperty("animation-name");
132
+ event.target.style.removeProperty("display");
133
+ event.target.removeEventListener("animationend", removeEvents);
134
+ };
135
135
 
136
- exports.Animation = Animation
136
+ exports.Animation = Animation;
@@ -77,4 +77,4 @@
77
77
  opacity: 0;
78
78
  transform: translateY(100%);
79
79
  }
80
- }
80
+ }
@@ -1,8 +1,14 @@
1
- const {contextBridge, ipcRenderer} = require("electron");
2
- const {SERVICES_CHANNELS, SERVICES_CALLS, BRIDGE_CHANNELS, BRIDGE_ARGS} = require("../chui_services/chui_channels");
1
+ const { contextBridge, ipcRenderer } = require("electron");
2
+ const {
3
+ SERVICES_CHANNELS,
4
+ SERVICES_CALLS,
5
+ SERVICES_SYNC_CALLS,
6
+ BRIDGE_CHANNELS,
7
+ BRIDGE_ARGS,
8
+ } = require("../chui_services/chui_channels");
3
9
  // Мост переиспользует ту же обёртку сервисов, что и страница: разница только в транспорте
4
10
  // (`Desktop` в preload'е сам вызовет ipcRenderer, потому что `window.chui` ему не виден)
5
- const {Desktop} = require("../chui_services/desktop");
11
+ const { Desktop } = require("../chui_services/desktop");
6
12
 
7
13
  /**
8
14
  * Preload безопасного режима (`Main({security: {contextIsolation: true}})`).
@@ -16,8 +22,10 @@ const {Desktop} = require("../chui_services/desktop");
16
22
  * файл при `DOMContentLoaded`.
17
23
  *
18
24
  * Формы API:
19
- * - `window.chui.desktop.{clipboard, recent, progress, shortcuts}` — то же, что `Desktop` из
25
+ * - `window.chui.desktop.{app, clipboard, recent, progress, shortcuts}` — то же, что `Desktop` из
20
26
  * `require('chuijs')`, поэтому код страницы переносится между режимами почти без правок;
27
+ * `desktop.app.quit()`/`restart()` закрывают и перезапускают всё приложение, а не окно; ответ
28
+ * у них значением, без промиса (синхронный канал);
21
29
  * - `window.chui.services` — плоская карта «метод → канал» (транспорт, который ищет `Desktop`);
22
30
  * - `window.chui.window` — минимизировать, развернуть, закрыть, состояние окна;
23
31
  * - `window.chui.theme` — текущая тема и подписка на её смену;
@@ -30,7 +38,10 @@ function argValue(prefix = String()) {
30
38
 
31
39
  const renderPath = argValue(BRIDGE_ARGS.RENDER);
32
40
  const csp = argValue(BRIDGE_ARGS.CSP);
33
- const channels = (argValue(BRIDGE_ARGS.CHANNELS) ?? "").split(",").map((channel) => channel.trim()).filter(Boolean);
41
+ const channels = (argValue(BRIDGE_ARGS.CHANNELS) ?? "")
42
+ .split(",")
43
+ .map((channel) => channel.trim())
44
+ .filter(Boolean);
34
45
 
35
46
  /**
36
47
  * Content-Security-Policy вставляется в документ до его скриптов: preload выполняется раньше
@@ -48,7 +59,7 @@ function applyCsp() {
48
59
  return true;
49
60
  }
50
61
 
51
- if (!applyCsp()) document.addEventListener("readystatechange", () => applyCsp(), {once: true});
62
+ if (!applyCsp()) document.addEventListener("readystatechange", () => applyCsp(), { once: true });
52
63
 
53
64
  const call = (method = String(), ...args) => {
54
65
  const channel = SERVICES_CALLS[method];
@@ -74,6 +85,12 @@ const services = {};
74
85
  for (const method of Object.keys(SERVICES_CALLS)) {
75
86
  services[method] = (...args) => call(method, ...args);
76
87
  }
88
+ // Синхронные вызовы — отдельным циклом и через `sendSync`: на синхронный канал `invoke`
89
+ // не отвечает, промис никогда бы не завершился. Конверт `{value}`/`{error}` разбирает
90
+ // `Desktop.#callSync`, поэтому мост отдаёт ответ как есть — как и обычный канал
91
+ for (const [method, channel] of Object.entries(SERVICES_SYNC_CALLS)) {
92
+ services[method] = (...args) => ipcRenderer.sendSync(channel, ...args);
93
+ }
77
94
  // Событие глобальной клавиши: подписка возвращает функцию отписки
78
95
  services.onShortcut = (handler = () => {}) => {
79
96
  const listener = (_event, accelerator) => handler(accelerator);
@@ -92,7 +109,7 @@ contextBridge.exposeInMainWorld("chui", {
92
109
  versions: {
93
110
  node: process.versions.node,
94
111
  chrome: process.versions.chrome,
95
- electron: process.versions.electron
112
+ electron: process.versions.electron,
96
113
  },
97
114
  platform: process.platform,
98
115
  /** Путь к странице окна (как он задан в опции `render`) */
@@ -112,53 +129,64 @@ contextBridge.exposeInMainWorld("chui", {
112
129
  copy: (text = String()) => Desktop.clipboard.history.copy(text),
113
130
  remove: (target = undefined) => Desktop.clipboard.history.remove(target),
114
131
  clear: () => Desktop.clipboard.history.clear(),
115
- onChange: (listener = () => {}) => services.onClipboardHistory(listener)
116
- }
132
+ onChange: (listener = () => {}) => services.onClipboardHistory(listener),
133
+ },
117
134
  },
118
135
  recent: {
119
136
  add: (path = String(), name = undefined) => Desktop.recent.add(path, name),
120
137
  clear: () => Desktop.recent.clear(),
121
- list: () => Desktop.recent.list()
138
+ list: () => Desktop.recent.list(),
122
139
  },
123
140
  progress: {
124
141
  set: (value = Number(), mode = "normal") => Desktop.progress.set(value, mode),
125
- clear: () => Desktop.progress.clear()
142
+ clear: () => Desktop.progress.clear(),
126
143
  },
127
144
  shell: {
128
- openExternal: (url = String()) => Desktop.shell.openExternal(url)
145
+ openExternal: (url = String()) => Desktop.shell.openExternal(url),
129
146
  },
130
147
  app: {
148
+ // Путь приходит значением, без промиса (синхронный канал), а `Desktop` в preload'е
149
+ // идёт в него сам: `window.chui` своей же странице здесь ещё не виден
131
150
  path: (name = String()) => Desktop.app.path(name),
132
- info: () => Desktop.app.info()
151
+ info: () => Desktop.app.info(),
152
+ // Сырые модули Electron странице не отдают. Методы есть для единообразия
153
+ // с `Desktop`, но со страницы бросают ошибку с подсказкой, что взять взамен
154
+ get: () => Desktop.app.get(),
155
+ getSession: () => Desktop.app.getSession(),
156
+ getWebContents: () => Desktop.app.getWebContents(),
157
+ // Закрытие и перезапуск — как у `Desktop` из пакета: те же шаги, что у пункта меню.
158
+ // Отвечают значением, а не промисом (синхронный канал), поэтому `await` не нужен
159
+ quit: () => Desktop.app.quit(),
160
+ restart: () => Desktop.app.restart(),
133
161
  },
134
162
  dialog: {
135
163
  open: (options = {}) => Desktop.dialog.open(options),
136
164
  save: (options = {}) => Desktop.dialog.save(options),
137
- message: (options = {}) => Desktop.dialog.message(options)
165
+ message: (options = {}) => Desktop.dialog.message(options),
138
166
  },
139
167
  session: {
140
168
  getCacheSize: () => Desktop.session.getCacheSize(),
141
169
  clearCache: () => Desktop.session.clearCache(),
142
- clearStorageData: (options = {}) => Desktop.session.clearStorageData(options)
170
+ clearStorageData: (options = {}) => Desktop.session.clearStorageData(options),
143
171
  },
144
172
  print: {
145
173
  page: (options = {}) => Desktop.print.page(options),
146
174
  html: (html = String(), options = {}) => Desktop.print.html(html, options),
147
- pdf: (html = String(), options = {}) => Desktop.print.pdf(html, options)
175
+ pdf: (html = String(), options = {}) => Desktop.print.pdf(html, options),
148
176
  },
149
177
  shortcuts: {
150
178
  on: (accelerator = String(), handler = () => {}) => Desktop.shortcuts.on(accelerator, handler),
151
179
  off: (accelerator = String(), handler = () => {}) => Desktop.shortcuts.off(accelerator, handler),
152
180
  offAll: () => Desktop.shortcuts.offAll(),
153
- get: () => Desktop.shortcuts.get()
154
- }
181
+ get: () => Desktop.shortcuts.get(),
182
+ },
155
183
  },
156
184
  window: {
157
185
  minimize: () => windowAction("minimize"),
158
186
  toggleMaximize: () => windowAction("toggle-maximize"),
159
187
  restore: () => windowAction("restore"),
160
188
  close: () => windowAction("close"),
161
- getState: () => ipcRenderer.invoke(BRIDGE_CHANNELS.WINDOW_STATE)
189
+ getState: () => ipcRenderer.invoke(BRIDGE_CHANNELS.WINDOW_STATE),
162
190
  },
163
191
  theme: {
164
192
  get: () => ipcRenderer.invoke(BRIDGE_CHANNELS.THEME_GET),
@@ -168,7 +196,7 @@ contextBridge.exposeInMainWorld("chui", {
168
196
  onChange: (listener = () => {}) => {
169
197
  themeListeners.add(listener);
170
198
  return () => themeListeners.delete(listener);
171
- }
199
+ },
172
200
  },
173
201
  locale: {
174
202
  get: () => ipcRenderer.invoke(BRIDGE_CHANNELS.LOCALE_GET),
@@ -177,17 +205,19 @@ contextBridge.exposeInMainWorld("chui", {
177
205
  onChange: (listener = () => {}) => {
178
206
  localeListeners.add(listener);
179
207
  return () => localeListeners.delete(listener);
180
- }
208
+ },
181
209
  },
182
210
  messaging: {
183
211
  /** Каналы задаёт приложение: `security: {channels: ['preview:update']}` */
184
212
  channels: [...channels],
185
213
  send: (channel = String(), ...args) => {
186
- if (!channels.includes(channel)) throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
214
+ if (!channels.includes(channel))
215
+ throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
187
216
  return ipcRenderer.invoke(BRIDGE_CHANNELS.MESSAGE, channel, ...args);
188
217
  },
189
218
  on: (channel = String(), listener = () => {}) => {
190
- if (!channels.includes(channel)) throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
219
+ if (!channels.includes(channel))
220
+ throw new Error(`chui: канал «${channel}» не разрешён в security.channels`);
191
221
  const listeners = messageListeners.get(channel) ?? new Set();
192
222
  listeners.add(listener);
193
223
  messageListeners.set(channel, listeners);
@@ -195,8 +225,8 @@ contextBridge.exposeInMainWorld("chui", {
195
225
  listeners.delete(listener);
196
226
  if (listeners.size === 0) messageListeners.delete(channel);
197
227
  };
198
- }
199
- }
228
+ },
229
+ },
200
230
  });
201
231
 
202
232
  // Скрипт страницы подключается здесь: в безопасном режиме `render` — это скрипт документа,
@@ -208,6 +238,6 @@ if (renderPath !== undefined) {
208
238
  script.defer = true;
209
239
  document.head.appendChild(script);
210
240
  };
211
- if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", addPageScript, {once: true});
241
+ if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", addPageScript, { once: true });
212
242
  else addPageScript();
213
243
  }