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,8 +1,8 @@
1
- const {globalShortcut, clipboard, app, dialog, nativeImage, nativeTheme, shell, BrowserWindow} = require("electron");
1
+ const { globalShortcut, clipboard, app, dialog, nativeImage, nativeTheme, shell, BrowserWindow } = require("electron");
2
2
  const fs = require("fs");
3
- const {Log} = require("../chui_logger/chui_logger");
4
- const {SERVICES_CHANNELS} = require("./chui_channels");
5
- const {I18n} = require("../chui_i18n/chui_i18n");
3
+ const { Log } = require("../chui_logger/chui_logger");
4
+ const { SERVICES_CHANNELS } = require("./chui_channels");
5
+ const { I18n } = require("../chui_i18n/chui_i18n");
6
6
 
7
7
  /**
8
8
  * Десктоп-сервисы main-процесса. Имена IPC-каналов живут в `chui_channels.js` — тот модуль
@@ -173,7 +173,7 @@ class RecentDocuments {
173
173
  #items = [];
174
174
  #limit = RecentDocuments.MAX;
175
175
 
176
- constructor(options = {limit: RecentDocuments.MAX}) {
176
+ constructor(options = { limit: RecentDocuments.MAX }) {
177
177
  if (options.limit !== undefined) this.#limit = options.limit;
178
178
  }
179
179
 
@@ -183,8 +183,10 @@ class RecentDocuments {
183
183
  }
184
184
  app.addRecentDocument(path);
185
185
  // Один и тот же файл не должен появляться дважды
186
- this.#items = [{path: path, name: name === undefined ? path : name}, ...this.#items.filter((item) => item.path !== path)]
187
- .slice(0, this.#limit);
186
+ this.#items = [
187
+ { path: path, name: name === undefined ? path : name },
188
+ ...this.#items.filter((item) => item.path !== path),
189
+ ].slice(0, this.#limit);
188
190
  return this;
189
191
  }
190
192
 
@@ -207,7 +209,7 @@ class RecentDocuments {
207
209
  return {
208
210
  role: "recentDocuments",
209
211
  label: pageTitle,
210
- submenu: [{label: clearLabel, role: "clearRecentDocuments"}]
212
+ submenu: [{ label: clearLabel, role: "clearRecentDocuments" }],
211
213
  };
212
214
  }
213
215
  }
@@ -223,7 +225,7 @@ class TaskbarProgress {
223
225
  NORMAL: "normal",
224
226
  ERROR: "error",
225
227
  PAUSED: "paused",
226
- INDETERMINATE: "indeterminate"
228
+ INDETERMINATE: "indeterminate",
227
229
  };
228
230
  #window = undefined;
229
231
  #value = 0;
@@ -285,7 +287,7 @@ class TaskbarProgress {
285
287
  const window = this.#window;
286
288
  if (window === undefined || window === null || typeof window.setProgressBar !== "function") return;
287
289
  if (window.isDestroyed()) return;
288
- window.setProgressBar(value, {mode: mode});
290
+ window.setProgressBar(value, { mode: mode });
289
291
  }
290
292
 
291
293
  /**
@@ -320,7 +322,7 @@ class Theme {
320
322
  static SOURCE = {
321
323
  SYSTEM: "system",
322
324
  LIGHT: "light",
323
- DARK: "dark"
325
+ DARK: "dark",
324
326
  };
325
327
  static #KEY = "theme_source";
326
328
  #store = undefined;
@@ -414,7 +416,7 @@ class ClipboardHistory {
414
416
  #limit = ClipboardHistory.MAX;
415
417
  #store = undefined;
416
418
 
417
- constructor(options = {limit: ClipboardHistory.MAX, store: undefined}) {
419
+ constructor(options = { limit: ClipboardHistory.MAX, store: undefined }) {
418
420
  if (options.limit !== undefined) this.#limit = options.limit;
419
421
  this.#store = options.store;
420
422
  this.restore();
@@ -477,7 +479,7 @@ class ClipboardHistory {
477
479
  }
478
480
  }
479
481
 
480
- exports.Shortcuts = Shortcuts
482
+ exports.Shortcuts = Shortcuts;
481
483
  /**
482
484
  * Печать: текущее окно, произвольный HTML и PDF.
483
485
  *
@@ -492,15 +494,18 @@ exports.Shortcuts = Shortcuts
492
494
  class Print {
493
495
  static DEFAULT_OPTIONS = {
494
496
  printBackground: true,
495
- pageSize: "A4"
497
+ pageSize: "A4",
496
498
  };
497
499
 
498
500
  /** Печать окна; возвращает `{success, failureReason}` — печать асинхронна и не бросает */
499
501
  page(window = undefined, options = {}) {
500
- if (window === undefined || window === null || window.isDestroyed()) return Promise.resolve({success: false, failureReason: I18n.t("feedback.printNoWindow")});
501
- const settings = {...Print.DEFAULT_OPTIONS, ...options};
502
+ if (window === undefined || window === null || window.isDestroyed())
503
+ return Promise.resolve({ success: false, failureReason: I18n.t("feedback.printNoWindow") });
504
+ const settings = { ...Print.DEFAULT_OPTIONS, ...options };
502
505
  return new Promise((resolve) => {
503
- window.webContents.print(settings, (success, failureReason) => resolve({success: success, failureReason: failureReason}));
506
+ window.webContents.print(settings, (success, failureReason) =>
507
+ resolve({ success: success, failureReason: failureReason })
508
+ );
504
509
  });
505
510
  }
506
511
 
@@ -526,19 +531,22 @@ class Print {
526
531
  let file = options.path;
527
532
  if (file === undefined) {
528
533
  const suggested = options.fileName === undefined ? "document.pdf" : options.fileName;
529
- const result = await dialog.showSaveDialog(options.parent, {defaultPath: suggested, filters: [{name: "PDF", extensions: ["pdf"]}]});
530
- if (result.canceled || result.filePath === undefined) return {saved: false, canceled: true};
534
+ const result = await dialog.showSaveDialog(options.parent, {
535
+ defaultPath: suggested,
536
+ filters: [{ name: "PDF", extensions: ["pdf"] }],
537
+ });
538
+ if (result.canceled || result.filePath === undefined) return { saved: false, canceled: true };
531
539
  file = result.filePath;
532
540
  }
533
541
  fs.writeFileSync(file, data);
534
- return {saved: true, path: file, canceled: false};
542
+ return { saved: true, path: file, canceled: false };
535
543
  } finally {
536
544
  if (!window.isDestroyed()) window.close();
537
545
  }
538
546
  }
539
547
 
540
548
  static #pdfOptions(options = {}) {
541
- const pdf = {...Print.DEFAULT_OPTIONS};
549
+ const pdf = { ...Print.DEFAULT_OPTIONS };
542
550
  for (const key of ["landscape", "pageSize", "scale", "printBackground", "margins", "preferCSSPageSize"]) {
543
551
  if (options[key] !== undefined) pdf[key] = options[key];
544
552
  }
@@ -550,7 +558,7 @@ class Print {
550
558
  const window = new BrowserWindow({
551
559
  show: false,
552
560
  parent: options.parent,
553
- webPreferences: {sandbox: true, contextIsolation: true, nodeIntegration: false}
561
+ webPreferences: { sandbox: true, contextIsolation: true, nodeIntegration: false },
554
562
  });
555
563
  const source = `data:text/html;charset=utf-8,${encodeURIComponent(String(html))}`;
556
564
  await window.loadURL(source);
@@ -558,12 +566,12 @@ class Print {
558
566
  }
559
567
  }
560
568
 
561
- exports.Shell = Shell
562
- exports.Clipboard = Clipboard
563
- exports.ClipboardHistory = ClipboardHistory
564
- exports.Theme = Theme
565
- exports.Locale = Locale
566
- exports.RecentDocuments = RecentDocuments
567
- exports.Print = Print
568
- exports.TaskbarProgress = TaskbarProgress
569
- exports.SERVICES_CHANNELS = SERVICES_CHANNELS
569
+ exports.Shell = Shell;
570
+ exports.Clipboard = Clipboard;
571
+ exports.ClipboardHistory = ClipboardHistory;
572
+ exports.Theme = Theme;
573
+ exports.Locale = Locale;
574
+ exports.RecentDocuments = RecentDocuments;
575
+ exports.Print = Print;
576
+ exports.TaskbarProgress = TaskbarProgress;
577
+ exports.SERVICES_CHANNELS = SERVICES_CHANNELS;
@@ -1,4 +1,4 @@
1
- const {SERVICES_CHANNELS, SERVICES_CALLS} = require("./chui_channels");
1
+ const { SERVICES_CHANNELS, SERVICES_CALLS, SERVICES_SYNC_CALLS, APP_PATH_NAMES } = require("./chui_channels");
2
2
 
3
3
  /**
4
4
  * Десктоп-сервисы со стороны страницы: буфер обмена, последние документы, прогресс на иконке
@@ -43,25 +43,25 @@ class Desktop {
43
43
  remove: (target = undefined) => Desktop.#call("clipboardHistoryRemove", target),
44
44
  clear: () => Desktop.#call("clipboardHistoryClear"),
45
45
  /** Подписка на изменение истории; возвращает функцию отписки */
46
- onChange: (listener = () => {}) => Desktop.#subscribeHistory(listener)
47
- }
46
+ onChange: (listener = () => {}) => Desktop.#subscribeHistory(listener),
47
+ },
48
48
  };
49
49
 
50
50
  static recent = {
51
51
  add: (path = String(), name = undefined) => Desktop.#call("recentAdd", path, name),
52
52
  clear: () => Desktop.#call("recentClear"),
53
- list: () => Desktop.#call("recentList")
53
+ list: () => Desktop.#call("recentList"),
54
54
  };
55
55
 
56
56
  static progress = {
57
57
  /** `progress.set(0.4)` — 40%; `mode` — TaskbarProgress.MODE */
58
58
  set: (value = Number(), mode = "normal") => Desktop.#call("progressSet", value, mode),
59
- clear: () => Desktop.#call("progressClear")
59
+ clear: () => Desktop.#call("progressClear"),
60
60
  };
61
61
 
62
62
  static shell = {
63
63
  /** Открыть адрес в браузере по умолчанию; `false` — схема не разрешена */
64
- openExternal: (url = String()) => Desktop.#call("shellOpenExternal", String(url))
64
+ openExternal: (url = String()) => Desktop.#call("shellOpenExternal", String(url)),
65
65
  };
66
66
 
67
67
  /**
@@ -78,7 +78,7 @@ class Desktop {
78
78
  page: (options = {}) => Desktop.#call("printPage", options),
79
79
  html: (html = String(), options = {}) => Desktop.#call("printHtml", String(html), options),
80
80
  /** `{saved, path, canceled}`; с `options.path` диалог сохранения не открывается */
81
- pdf: (html = String(), options = {}) => Desktop.#call("printPdf", String(html), options)
81
+ pdf: (html = String(), options = {}) => Desktop.#call("printPdf", String(html), options),
82
82
  };
83
83
 
84
84
  static shortcuts = {
@@ -86,7 +86,7 @@ class Desktop {
86
86
  off: (accelerator = String(), handler = () => {}) => Desktop.#unsubscribe(accelerator, handler),
87
87
  offAll: () => Desktop.#unsubscribeAll(),
88
88
  /** Комбинации, на которые подписано это окно */
89
- get: () => [...Desktop.#shortcut_handlers.keys()]
89
+ get: () => [...Desktop.#shortcut_handlers.keys()],
90
90
  };
91
91
 
92
92
  /**
@@ -99,14 +99,14 @@ class Desktop {
99
99
  * ```
100
100
  */
101
101
  static theme = {
102
- SOURCE: {SYSTEM: "system", LIGHT: "light", DARK: "dark"},
102
+ SOURCE: { SYSTEM: "system", LIGHT: "light", DARK: "dark" },
103
103
  /** @returns {Promise<{theme: string, source: string}>} фактическая тема и выбранный источник */
104
104
  get: () => Desktop.#call("themeGet"),
105
105
  set: (source = String()) => Desktop.#call("themeSet", String(source)),
106
106
  /** Переключить светлую и тёмную */
107
107
  toggle: () => Desktop.#call("themeToggle"),
108
108
  /** Подписка на смену темы; возвращает функцию отписки */
109
- onChange: (listener = () => {}) => Desktop.#subscribeTheme(listener)
109
+ onChange: (listener = () => {}) => Desktop.#subscribeTheme(listener),
110
110
  };
111
111
 
112
112
  /**
@@ -122,23 +122,79 @@ class Desktop {
122
122
  get: () => Desktop.#call("localeGet"),
123
123
  set: (locale = String()) => Desktop.#call("localeSet", String(locale)),
124
124
  /** Подписка на смену языка; возвращает функцию отписки */
125
- onChange: (listener = () => {}) => Desktop.#subscribeLocale(listener)
125
+ onChange: (listener = () => {}) => Desktop.#subscribeLocale(listener),
126
126
  };
127
127
 
128
128
  /**
129
- * Приложение со стороны страницы: пути и сведения. Прямого доступа к модулям Electron
130
- * у страницы нет — это и есть рекомендация Electron вместо `remote`.
129
+ * Приложение: пути, сведения и сырые модули Electron.
130
+ *
131
+ * Странице модули main не отдают — это и есть рекомендация Electron вместо `remote`,
132
+ * поэтому пути и сведения идут по IPC. В main-процессе IPC нет, и те же методы отвечают
133
+ * значением: `path()` берёт путь у Electron, а `get()`/`getSession()`/`getWebContents()`
134
+ * отдают сами объекты.
131
135
  *
132
136
  * ```javascript
133
- * await Desktop.app.path('userData'); // каталог настроек приложения
134
- * await Desktop.app.info(); // {name, version, locale, isPackaged}
137
+ * const dir = Desktop.app.path('userData'); // путь приходит сразу, без промиса
138
+ * const info = await Desktop.app.info(); // {name, version, locale, isPackaged}
139
+ * Desktop.app.quit(); // закрыть приложение — тоже без промиса
135
140
  * ```
136
141
  */
137
142
  static app = {
138
- /** Путь по имени из белого списка (`"userData"`, `"downloads"`, …) */
139
- path: (name = String()) => Desktop.#call("appPath", String(name)),
143
+ /**
144
+ * Путь по имени из белого списка (`"userData"`, `"downloads"`, …).
145
+ *
146
+ * Отвечает значением, а не промисом: из этих путей страница собирает свои (файлы,
147
+ * логи, кеш) на старте, и `await` пришлось бы протаскивать через весь код инициализации.
148
+ * Идёт по синхронному каналу (`SERVICES_SYNC_CALLS`), а потому значение берётся один раз
149
+ * на имя и кешируется — `sendSync` останавливает страницу на время ответа, а путь за
150
+ * жизнь приложения не меняется (`app.setPath` в main — редкий случай, и тогда кеш
151
+ * страницы устареет так же, как устарел бы вызов, сделанный раньше).
152
+ * Прежний `await Desktop.app.path(...)` продолжает работать: `await` на строке
153
+ * возвращает ту же строку.
154
+ *
155
+ * Работает и в main-процессе: там канала нет, и путь берётся у Electron напрямую
156
+ * (с тем же белым списком), поэтому `main.js` обходится без отдельного класса путей.
157
+ */
158
+ path: (name = String()) => Desktop.#appPath(String(name)),
140
159
  /** @returns {Promise<{name: string, version: string, locale: string, systemLocale: string, isPackaged: boolean}>} */
141
- info: () => Desktop.#call("appInfo")
160
+ info: () => Desktop.#call("appInfo"),
161
+ /**
162
+ * Сырой объект `app` Electron. **Только для main-процесса:** странице модули main
163
+ * не отдают, и здесь она получит ошибку с подсказкой — вместо `app` со страницы берут
164
+ * `Desktop.app.info()`, `Desktop.session.*`, `Desktop.dialog` или свой канал.
165
+ *
166
+ * ```javascript
167
+ * const app = Desktop.app.get(); // main.js
168
+ * app.on('session-created', (session) => {});
169
+ * ```
170
+ */
171
+ get: () => Desktop.#electronObject("app"),
172
+ /** Сырой `session` Electron (только main-процесс; странице — `Desktop.session.*`) */
173
+ getSession: () => Desktop.#electronObject("session"),
174
+ /** Сырой `webContents` Electron (только main-процесс) */
175
+ getWebContents: () => Desktop.#electronObject("webContents"),
176
+ /**
177
+ * Закрыть приложение: тот же выход, что у пункта меню «Выход» — `Main.stop()` снимает
178
+ * обработчики сервисов, глобальные клавиши и подписки, а затем приложение выходит.
179
+ *
180
+ * Отвечает значением, а не промисом (синхронный канал): ждать результат не нужно и
181
+ * `await` ничего не меняет — `await true` даёт то же `true`. Ответ приходит до выхода,
182
+ * потому что main откладывает `stop()` через `setImmediate`.
183
+ *
184
+ * ```javascript
185
+ * new Button({title: 'Выход', clickEvent: () => Desktop.app.quit()});
186
+ * ```
187
+ * @returns {boolean} `true` — выход начат
188
+ */
189
+ quit: () => Desktop.#callSync("appQuitSync"),
190
+ /**
191
+ * Перезапустить приложение: процесс поднимается заново (`Main.restart()` →
192
+ * `app.relaunch()`), поэтому состояние из памяти не переносится — сохраните его сами.
193
+ *
194
+ * Отвечает значением, а не промисом (синхронный канал), как и `quit()`.
195
+ * @returns {boolean} `true` — перезапуск начат
196
+ */
197
+ restart: () => Desktop.#callSync("appRestartSync"),
142
198
  };
143
199
 
144
200
  /**
@@ -152,7 +208,7 @@ class Desktop {
152
208
  static dialog = {
153
209
  open: (options = Object()) => Desktop.#call("dialogOpen", options),
154
210
  save: (options = Object()) => Desktop.#call("dialogSave", options),
155
- message: (options = Object()) => Desktop.#call("dialogMessage", options)
211
+ message: (options = Object()) => Desktop.#call("dialogMessage", options),
156
212
  };
157
213
 
158
214
  /**
@@ -167,7 +223,7 @@ class Desktop {
167
223
  /** @returns {Promise<number>} размер кеша в байтах */
168
224
  getCacheSize: () => Desktop.#call("sessionCacheSize"),
169
225
  clearCache: () => Desktop.#call("sessionClearCache"),
170
- clearStorageData: (options = Object()) => Desktop.#call("sessionClearStorage", options)
226
+ clearStorageData: (options = Object()) => Desktop.#call("sessionClearStorage", options),
171
227
  };
172
228
 
173
229
  // accelerator → набор обработчиков этого окна
@@ -183,6 +239,9 @@ class Desktop {
183
239
  static #history_handlers = new Set();
184
240
  static #history_event_bound = false;
185
241
  static #history_bridge_unsubscribe = undefined;
242
+ // Пути приложения, уже полученные значением: `sendSync` останавливает страницу на время
243
+ // ответа, поэтому за одним и тем же именем в main ходят один раз
244
+ static #app_paths = new Map();
186
245
 
187
246
  static #bridge() {
188
247
  if (typeof window === "undefined" || window.chui === undefined) return undefined;
@@ -193,10 +252,70 @@ class Desktop {
193
252
  static #call(method = String(), ...args) {
194
253
  const services = Desktop.#bridge();
195
254
  if (services !== undefined && typeof services[method] === "function") return services[method](...args);
196
- const {ipcRenderer} = require("electron");
255
+ const { ipcRenderer } = require("electron");
197
256
  return ipcRenderer.invoke(SERVICES_CALLS[method], ...args);
198
257
  }
199
258
 
259
+ /**
260
+ * Синхронный вызов сервиса: в безопасном режиме — метод моста, иначе — канал `sendSync`.
261
+ * Канал отвечает конвертом `{value}`/`{error}` (см. `SERVICES_SYNC_CALLS` в chui_channels.js),
262
+ * поэтому исключение из main превращается здесь в обычную ошибку с его текстом.
263
+ */
264
+ static #callSync(method = String(), ...args) {
265
+ const services = Desktop.#bridge();
266
+ const answer =
267
+ services !== undefined && typeof services[method] === "function"
268
+ ? services[method](...args)
269
+ : require("electron").ipcRenderer.sendSync(SERVICES_SYNC_CALLS[method], ...args);
270
+ if (answer === null || typeof answer !== "object") return answer;
271
+ if (answer.error !== undefined) throw new Error(answer.error);
272
+ return answer.value;
273
+ }
274
+
275
+ /**
276
+ * Путь приложения значением (см. `Desktop.app.path`).
277
+ *
278
+ * В main-процессе IPC недоступен (`ipcRenderer` там нет, а отправителя-страницы не
279
+ * существует), поэтому путь берётся у Electron напрямую — с той же проверкой белого
280
+ * списка, что и в канале. Это позволяет звать `Desktop.app.path()` из `main.js` и со
281
+ * страницы одним и тем же вызовом.
282
+ */
283
+ static #appPath(name = String()) {
284
+ if (Desktop.#app_paths.has(name)) return Desktop.#app_paths.get(name);
285
+ const value = Desktop.#inMainProcess() ? Desktop.#appPathInMain(name) : Desktop.#callSync("appPathSync", name);
286
+ Desktop.#app_paths.set(name, value);
287
+ return value;
288
+ }
289
+
290
+ /** Различает процессы: у main нет `window`, у страницы и preload'а — есть */
291
+ static #inMainProcess() {
292
+ return typeof window === "undefined";
293
+ }
294
+
295
+ /** main-процесс: путь у Electron напрямую, но только из белого списка */
296
+ static #appPathInMain(name = String()) {
297
+ if (APP_PATH_NAMES.includes(name) === false) {
298
+ throw new Error(`Desktop.app.path: имя «${name}» вне белого списка`);
299
+ }
300
+ return require("electron").app.getPath(name);
301
+ }
302
+
303
+ /**
304
+ * Сырой объект Electron для main-процесса.
305
+ *
306
+ * Со страницы `app`/`session`/`webContents` недоступны: Electron не рекомендует `remote`,
307
+ * а мост preload'а отдаёт только узкий `window.chui`. Поэтому вместо тихого `undefined`
308
+ * здесь ошибка с подсказкой, что взять взамен.
309
+ */
310
+ static #electronObject(name = String()) {
311
+ if (Desktop.#inMainProcess() === false) {
312
+ throw new Error(
313
+ `Desktop.app: сырой «${name}» доступен только в main-процессе — со страницы возьмите Desktop.app.info(), Desktop.session.* или свой канал из security.channels`
314
+ );
315
+ }
316
+ return require("electron")[name];
317
+ }
318
+
200
319
  static #subscribe(accelerator = String(), handler = () => {}) {
201
320
  const handlers = Desktop.#shortcut_handlers.get(accelerator) ?? new Set();
202
321
  const first = handlers.size === 0;
@@ -243,7 +362,7 @@ class Desktop {
243
362
  Desktop.#shortcut_bridge_unsubscribe = services.onShortcut(Desktop.#onShortcutEvent);
244
363
  return;
245
364
  }
246
- const {ipcRenderer} = require("electron");
365
+ const { ipcRenderer } = require("electron");
247
366
  ipcRenderer.on(SERVICES_CHANNELS.SHORTCUT_EVENT, Desktop.#ipcShortcutEvent);
248
367
  }
249
368
 
@@ -255,7 +374,7 @@ class Desktop {
255
374
  Desktop.#shortcut_bridge_unsubscribe = undefined;
256
375
  return;
257
376
  }
258
- const {ipcRenderer} = require("electron");
377
+ const { ipcRenderer } = require("electron");
259
378
  ipcRenderer.removeListener(SERVICES_CHANNELS.SHORTCUT_EVENT, Desktop.#ipcShortcutEvent);
260
379
  }
261
380
 
@@ -288,7 +407,7 @@ class Desktop {
288
407
  Desktop.#theme_bridge_unsubscribe = theme.onChange(Desktop.#onThemeEvent);
289
408
  return;
290
409
  }
291
- const {ipcRenderer} = require("electron");
410
+ const { ipcRenderer } = require("electron");
292
411
  ipcRenderer.on(SERVICES_CHANNELS.THEME_EVENT, Desktop.#ipcThemeEvent);
293
412
  }
294
413
 
@@ -300,7 +419,7 @@ class Desktop {
300
419
  Desktop.#theme_bridge_unsubscribe = undefined;
301
420
  return;
302
421
  }
303
- const {ipcRenderer} = require("electron");
422
+ const { ipcRenderer } = require("electron");
304
423
  ipcRenderer.removeListener(SERVICES_CHANNELS.THEME_EVENT, Desktop.#ipcThemeEvent);
305
424
  }
306
425
 
@@ -333,7 +452,7 @@ class Desktop {
333
452
  Desktop.#locale_bridge_unsubscribe = locale.onChange(Desktop.#onLocaleEvent);
334
453
  return;
335
454
  }
336
- const {ipcRenderer} = require("electron");
455
+ const { ipcRenderer } = require("electron");
337
456
  ipcRenderer.on(SERVICES_CHANNELS.LOCALE_EVENT, Desktop.#ipcLocaleEvent);
338
457
  }
339
458
 
@@ -345,7 +464,7 @@ class Desktop {
345
464
  Desktop.#locale_bridge_unsubscribe = undefined;
346
465
  return;
347
466
  }
348
- const {ipcRenderer} = require("electron");
467
+ const { ipcRenderer } = require("electron");
349
468
  ipcRenderer.removeListener(SERVICES_CHANNELS.LOCALE_EVENT, Desktop.#ipcLocaleEvent);
350
469
  }
351
470
 
@@ -376,7 +495,7 @@ class Desktop {
376
495
  Desktop.#history_bridge_unsubscribe = services.onClipboardHistory(Desktop.#onHistoryEvent);
377
496
  return;
378
497
  }
379
- const {ipcRenderer} = require("electron");
498
+ const { ipcRenderer } = require("electron");
380
499
  ipcRenderer.on(SERVICES_CHANNELS.CLIPBOARD_HISTORY_EVENT, Desktop.#ipcHistoryEvent);
381
500
  }
382
501
 
@@ -388,7 +507,7 @@ class Desktop {
388
507
  Desktop.#history_bridge_unsubscribe = undefined;
389
508
  return;
390
509
  }
391
- const {ipcRenderer} = require("electron");
510
+ const { ipcRenderer } = require("electron");
392
511
  ipcRenderer.removeListener(SERVICES_CHANNELS.CLIPBOARD_HISTORY_EVENT, Desktop.#ipcHistoryEvent);
393
512
  }
394
513
 
@@ -397,4 +516,4 @@ class Desktop {
397
516
  };
398
517
  }
399
518
 
400
- exports.Desktop = Desktop
519
+ exports.Desktop = Desktop;
@@ -1,4 +1,4 @@
1
- const {setStyles} = require("../chui_functions");
1
+ const { setStyles } = require("../chui_functions");
2
2
 
3
3
  /**
4
4
  * Перестановка элементов внутри контейнера: строки списка, карточки, пункты меню.
@@ -27,7 +27,7 @@ const {setStyles} = require("../chui_functions");
27
27
  class Sortable {
28
28
  static AXIS = {
29
29
  Y: "y",
30
- X: "x"
30
+ X: "x",
31
31
  };
32
32
 
33
33
  #container = undefined;
@@ -43,13 +43,16 @@ class Sortable {
43
43
  #baseline = [];
44
44
  #bound = false;
45
45
 
46
- constructor(container = new HTMLElement(), options = {
47
- itemSelector: String(),
48
- handleSelector: String(),
49
- axis: String(),
50
- enabled: Boolean(),
51
- onReorder: undefined
52
- }) {
46
+ constructor(
47
+ container = new HTMLElement(),
48
+ options = {
49
+ itemSelector: String(),
50
+ handleSelector: String(),
51
+ axis: String(),
52
+ enabled: Boolean(),
53
+ onReorder: undefined,
54
+ }
55
+ ) {
53
56
  setStyles(__dirname + "/styles.css", "chUiJS_Sortable");
54
57
  this.#container = container;
55
58
  this.#item_selector = options.itemSelector === undefined ? "*" : options.itemSelector;
@@ -172,7 +175,7 @@ class Sortable {
172
175
  this.#before = undefined;
173
176
  if (Sortable.#sameOrder(this.#order_before, this.#items())) return;
174
177
  this.#order_before = this.#items();
175
- if (this.#on_reorder !== undefined) this.#on_reorder(this.getOrder(), {source: source, before: before});
178
+ if (this.#on_reorder !== undefined) this.#on_reorder(this.getOrder(), { source: source, before: before });
176
179
  }
177
180
 
178
181
  static #sameOrder(left = [], right = []) {
@@ -218,7 +221,9 @@ class Sortable {
218
221
  const forward = this.#axis === Sortable.AXIS.X ? "ArrowRight" : "ArrowDown";
219
222
  if (event.key !== backward && event.key !== forward) return;
220
223
  const items = this.#items();
221
- const current = items.findIndex((item) => item === document.activeElement || item.contains(document.activeElement));
224
+ const current = items.findIndex(
225
+ (item) => item === document.activeElement || item.contains(document.activeElement)
226
+ );
222
227
  if (current === -1) return;
223
228
  const next = event.key === backward ? current - 1 : current + 1;
224
229
  if (next < 0 || next >= items.length) return;
@@ -235,4 +240,4 @@ class Sortable {
235
240
  };
236
241
  }
237
242
 
238
- exports.Sortable = Sortable
243
+ exports.Sortable = Sortable;
@@ -15,7 +15,7 @@
15
15
  * Общие тексты берутся из словаря текущего языка (`I18n`), поэтому и они переводятся
16
16
  * вместе с интерфейсом; свои тексты поля (`messages`) задаются строками и не переводятся.
17
17
  */
18
- const {I18n} = require("../chui_i18n/chui_i18n");
18
+ const { I18n } = require("../chui_i18n/chui_i18n");
19
19
 
20
20
  /** Ключ словаря для каждого правила: значения-параметры подставляет `I18n.t` */
21
21
  const MESSAGE_KEYS = {
@@ -25,7 +25,7 @@ const MESSAGE_KEYS = {
25
25
  minLength: "validate.minLength",
26
26
  maxLength: "validate.maxLength",
27
27
  pattern: "validate.pattern",
28
- email: "validate.email"
28
+ email: "validate.email",
29
29
  };
30
30
 
31
31
  // Сборник общих сообщений остаётся доступен как прежде, но текст берётся из текущей локали:
@@ -34,7 +34,7 @@ const DEFAULT_MESSAGES = {};
34
34
  for (const rule of Object.keys(MESSAGE_KEYS)) {
35
35
  Object.defineProperty(DEFAULT_MESSAGES, rule, {
36
36
  enumerable: true,
37
- get: () => I18n.t(MESSAGE_KEYS[rule])
37
+ get: () => I18n.t(MESSAGE_KEYS[rule]),
38
38
  });
39
39
  }
40
40
 
@@ -63,7 +63,10 @@ const fail = (rule, rules) => ({ valid: false, rule: rule, message: messageFor(r
63
63
 
64
64
  /** Проверка значения по правилам: {valid, rule?, message?} */
65
65
  function check(value, rules = {}) {
66
- const empty = typeof value === "boolean" ? value === false : String(value === undefined || value === null ? "" : value).trim() === "";
66
+ const empty =
67
+ typeof value === "boolean"
68
+ ? value === false
69
+ : String(value === undefined || value === null ? "" : value).trim() === "";
67
70
  if (empty) {
68
71
  return rules.required === true ? fail("required", rules) : { valid: true };
69
72
  }