chuijs 3.6.2 → 3.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1203 -162
  3. package/framework/appLayout/default/chui_app_layout.js +747 -747
  4. package/framework/appLayout/default/global_style.css +592 -568
  5. package/framework/appLayout/default/main_theme_style.css +118 -101
  6. package/framework/components/chui_accordion/accordion.js +63 -63
  7. package/framework/components/chui_accordion/styles.css +79 -75
  8. package/framework/components/chui_badge/badge.js +55 -55
  9. package/framework/components/chui_badge/styles.css +29 -29
  10. package/framework/components/chui_button/button.js +48 -48
  11. package/framework/components/chui_button/styles.css +75 -70
  12. package/framework/components/chui_calendar/calendar.js +97 -97
  13. package/framework/components/chui_card/card.js +87 -0
  14. package/framework/components/chui_card/styles.css +72 -0
  15. package/framework/components/chui_code/code.js +18 -18
  16. package/framework/components/chui_code/styles.css +9 -9
  17. package/framework/components/chui_console/console.js +27 -27
  18. package/framework/components/chui_console/styles.css +31 -31
  19. package/framework/components/chui_content_block/content_block.js +51 -51
  20. package/framework/components/chui_content_block/styles.css +2 -2
  21. package/framework/components/chui_context_menu/context_menu.js +230 -0
  22. package/framework/components/chui_context_menu/styles.css +136 -0
  23. package/framework/components/chui_custom_element/custom_element.js +24 -24
  24. package/framework/components/chui_details/details.js +85 -85
  25. package/framework/components/chui_details/styles.css +70 -66
  26. package/framework/components/chui_fieldset/fieldset.js +40 -40
  27. package/framework/components/chui_fieldset/styles.css +24 -24
  28. package/framework/components/chui_form/form.js +40 -40
  29. package/framework/components/chui_form/styles.css +21 -21
  30. package/framework/components/chui_graphs/graphs.js +234 -190
  31. package/framework/components/chui_graphs/styles.css +37 -32
  32. package/framework/components/chui_hero/hero.js +98 -0
  33. package/framework/components/chui_hero/styles.css +91 -0
  34. package/framework/components/chui_html_block/html_block.js +16 -16
  35. package/framework/components/chui_html_block/styles.css +5 -5
  36. package/framework/components/chui_hx/hx.js +15 -15
  37. package/framework/components/chui_hx/styles.css +38 -38
  38. package/framework/components/chui_icons/icons.js +2144 -2144
  39. package/framework/components/chui_icons/styles.css +13 -13
  40. package/framework/components/chui_inputs/chui_check_box/check_box.js +32 -32
  41. package/framework/components/chui_inputs/chui_check_box/styles.css +91 -86
  42. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +166 -150
  43. package/framework/components/chui_inputs/chui_combo_box/styles.css +113 -107
  44. package/framework/components/chui_inputs/chui_date/date.js +177 -174
  45. package/framework/components/chui_inputs/chui_date/styles.css +163 -128
  46. package/framework/components/chui_inputs/chui_email/email.js +61 -61
  47. package/framework/components/chui_inputs/chui_email/styles.css +69 -71
  48. package/framework/components/chui_inputs/chui_file/file.js +76 -76
  49. package/framework/components/chui_inputs/chui_file/styles.css +99 -99
  50. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +295 -295
  51. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +212 -235
  52. package/framework/components/chui_inputs/chui_number/number.js +108 -108
  53. package/framework/components/chui_inputs/chui_number/styles.css +84 -84
  54. package/framework/components/chui_inputs/chui_password/password.js +99 -99
  55. package/framework/components/chui_inputs/chui_password/styles.css +97 -97
  56. package/framework/components/chui_inputs/chui_radio_button/radio_button.js +45 -45
  57. package/framework/components/chui_inputs/chui_radio_button/styles.css +76 -73
  58. package/framework/components/chui_inputs/chui_select_box/select_box.js +187 -170
  59. package/framework/components/chui_inputs/chui_select_box/styles.css +123 -117
  60. package/framework/components/chui_inputs/chui_text/styles.css +83 -84
  61. package/framework/components/chui_inputs/chui_text/text.js +91 -91
  62. package/framework/components/chui_inputs/chui_text_area/styles.css +57 -57
  63. package/framework/components/chui_inputs/chui_text_area/text_area.js +61 -61
  64. package/framework/components/chui_inputs/chui_toggle/styles.css +66 -61
  65. package/framework/components/chui_inputs/chui_toggle/toggle.js +36 -36
  66. package/framework/components/chui_label/label.js +54 -54
  67. package/framework/components/chui_label/styles.css +11 -11
  68. package/framework/components/chui_media/audio.js +908 -908
  69. package/framework/components/chui_media/audio_styles.css +458 -458
  70. package/framework/components/chui_media/image.js +105 -105
  71. package/framework/components/chui_media/image_styles.css +61 -61
  72. package/framework/components/chui_media/video.js +248 -248
  73. package/framework/components/chui_media/video_styles.css +160 -160
  74. package/framework/components/chui_menu_bar/menu_bar.js +66 -74
  75. package/framework/components/chui_menu_bar/styles.css +114 -76
  76. package/framework/components/chui_modal/modal.js +142 -135
  77. package/framework/components/chui_modal/styles.css +36 -36
  78. package/framework/components/chui_notification/notification.js +72 -72
  79. package/framework/components/chui_notification/notification_download.js +78 -78
  80. package/framework/components/chui_notification/notification_download_progress.css +119 -123
  81. package/framework/components/chui_notification/notification_download_progress.js +105 -105
  82. package/framework/components/chui_notification/notification_update.js +54 -54
  83. package/framework/components/chui_notification/styles.css +91 -91
  84. package/framework/components/chui_notification/styles_download.css +85 -85
  85. package/framework/components/chui_notification/styles_update.css +73 -73
  86. package/framework/components/chui_paragraph/paragraph.js +14 -14
  87. package/framework/components/chui_paragraph/styles.css +6 -6
  88. package/framework/components/chui_popups/popups.js +250 -250
  89. package/framework/components/chui_popups/styles.css +129 -115
  90. package/framework/components/chui_progress_bar/progress_bar.js +28 -28
  91. package/framework/components/chui_progress_bar/styles.css +28 -28
  92. package/framework/components/chui_radio_group/radio_group.js +57 -57
  93. package/framework/components/chui_radio_group/styles.css +3 -3
  94. package/framework/components/chui_slideshow/slideshow.js +75 -75
  95. package/framework/components/chui_slideshow/styles.css +94 -85
  96. package/framework/components/chui_spinner/spinner.js +45 -45
  97. package/framework/components/chui_spinner/styles.css +23 -23
  98. package/framework/components/chui_table/styles.css +42 -42
  99. package/framework/components/chui_table/table.js +234 -234
  100. package/framework/components/chui_tabs/chui_tabs.js +77 -77
  101. package/framework/components/chui_tabs/styles.css +54 -48
  102. package/framework/components/chui_text_editor/styles.css +217 -217
  103. package/framework/components/chui_text_editor/text_editor.js +144 -144
  104. package/framework/components/chui_text_editor/text_editor_panel.js +568 -568
  105. package/framework/components/chui_tree_view/styles.css +81 -75
  106. package/framework/components/chui_tree_view/tree_view.js +90 -90
  107. package/framework/components/chui_webview/styles.css +27 -27
  108. package/framework/components/chui_webview/webview.js +103 -103
  109. package/framework/components/telegram_bot/chui_telegram_bot.js +183 -183
  110. package/framework/modules/chui_animations/animations.js +121 -121
  111. package/framework/modules/chui_animations/styles.css +79 -79
  112. package/framework/modules/chui_fonts.js +7129 -6974
  113. package/framework/modules/chui_functions.js +106 -78
  114. package/framework/modules/chui_logger/chui_logger.js +53 -53
  115. package/framework/modules/chui_page/page.js +86 -86
  116. package/framework/modules/chui_page/styles.css +9 -9
  117. package/framework/tray_bar/chui_menu_items.js +48 -48
  118. package/index.js +570 -555
  119. package/package.json +51 -46
package/README.md CHANGED
@@ -1,162 +1,1203 @@
1
- # chuijs
2
- ### CHROME_SANDBOX FIX
3
- ```shell
4
- cd node_modules/electron/dist && sudo chown root chrome-sandbox && sudo chmod 4755 chrome-sandbox
5
- ```
6
- ### Структура
7
- #### exampleApp / app / views / main.js
8
- ```javascript
9
- const { Page, Button, TextInput, Notification, Log } = require('chuijs');
10
- class MainPage extends Page {
11
- constructor() {
12
- super();
13
- this.setTitle('Сказать привет!');
14
- this.setMain(false)
15
-
16
- let name = new TextInput({
17
- title: 'Введите ваше имя',
18
- placeholder: 'Введите ваше имя',
19
- required: false
20
- });
21
- let hello = new Button({
22
- title: "Сказать привет!",
23
- clickEvent: () => {
24
- if (name.getValue() !== "") {
25
- new Notification({
26
- title: `Привет, ${name.getValue()}!`, text: `Привет, ${name.getValue()}!`,
27
- style: Notification.STYLE.SUCCESS, showTime: 5000
28
- }).show();
29
- } else {
30
- new Notification({
31
- title: `Привет мир!`, text: `Привет мир!`,
32
- style: Notification.STYLE.SUCCESS, showTime: 5000
33
- }).show();
34
- }
35
- }
36
- })
37
- this.add(name, hello)
38
- }
39
- }
40
- exports.MainPage = MainPage
41
- ```
42
- #### exampleApp / app / app.js
43
- ```javascript
44
- /** RENDERER ПРОЦЕСС */
45
- /** ИМПОРТЫ */
46
- const { AppLayout, render } = require('chuijs');
47
- /** СТРАНИЦЫ */
48
- const { MainPage } = require('../app/views/main');
49
- class App extends AppLayout {
50
- constructor() {
51
- super();
52
- /** РОУТЫ */
53
- this.setRoute(new MainPage());
54
- }
55
- }
56
- render(() => new App()).catch(err => Log.error(err))
57
- ```
58
- #### exampleApp / main.js
59
- ```javascript
60
- /** main.js */
61
- const { Main, MenuItem } = require('chuijs');
62
- const main = new Main({
63
- name: "exampleApp",
64
- width: 1366,
65
- height: 768,
66
- render: `${__dirname}/app/app.js`,
67
- devTools: true
68
- /** icon: `${__dirname}/resources/icons/app/icon.png` */
69
- });
70
- main.start({
71
- tray: [
72
- new MenuItem().button('Показать \ Скрыть', () => main.hideAndShow()),
73
- new MenuItem().separator(),
74
- new MenuItem().toggleDevTools('Консоль разработчика'),
75
- new MenuItem().separator(),
76
- new MenuItem().quit("Выход")
77
- ]
78
- })
79
- ```
80
- #### Donwload Session в main.js
81
- ```javascript
82
- const { App } = require('chuijs');
83
- App.get().on('session-created', (session) => {
84
- session.on('will-download', (e, item, contents) => {
85
- // ...
86
- });
87
- });
88
- ```
89
- #### TelegramBot API
90
- ```javascript
91
- const { TelegramBot } = require('chuijs');
92
- let bot = new TelegramBot("token");
93
- bot.setToken("token");
94
-
95
- // Функции
96
- async function run() {
97
- await bot.getMe();
98
- await bot.getUpdates();
99
- await bot.getChat("id");
100
- /* ... */
101
- }
102
- ```
103
- ### Модули
104
- | Компонент | Версия | Описание |
105
- |--------------------------------------|--------|-----------------------|
106
- | Автоматическое обновление приложения | 1.0.0 | только AppImage, NSIS |
107
- | Themes | 1.0.0 | Зависит от системы |
108
- | Route | 1.0.0 | |
109
- | Page | 1.0.0 | |
110
- | ElectronTray | 1.0.0 | |
111
- | TelegramBot API | 1.0.0 | |
112
-
113
- ### Компоненты
114
- | Компонент | Версия | Описание |
115
- |---------------------------------|--------|----------|
116
- | AppLayout | 1.0.0 | |
117
- | DateInput | 1.0.0 | |
118
- | NumberInput | 1.0.0 | |
119
- | EmailInput | 1.0.0 | |
120
- | TextArea | 1.0.0 | |
121
- | PasswordInput | 1.0.0 | |
122
- | FileInput | 1.0.0 | |
123
- | H1, H2, H3, H4, H5, H6 | 1.0.0 | |
124
- | Label | 1.0.0 | |
125
- | Paragraph | 1.0.0 | |
126
- | Button | 1.0.0 | |
127
- | CheckBox | 1.0.0 | |
128
- | ComboBox | 1.0.0 | |
129
- | MultiComboBox | 1.0.0 | |
130
- | SelectBox | 1.0.0 | |
131
- | ContentBlock | 1.0.0 | |
132
- | Details | 1.0.0 | |
133
- | Dialogs | 1.0.0 | |
134
- | RadioButton | 1.0.0 | |
135
- | Table | 1.0.0 | |
136
- | ProgressBar | 1.0.0 | |
137
- | Toggle | 1.0.0 | |
138
- | Tabs | 1.0.0 | |
139
- | Notification | 1.0.0 | |
140
- | Badge | 1.0.0 | |
141
- | Image | 1.0.0 | |
142
- | Icons | 1.0.0 | |
143
- | WebView | 1.0.0 | |
144
- | Spinner | 1.0.0 | |
145
- | GroupRadio | 1.0.0 | |
146
- | Accordion | 1.0.0 | |
147
- | Pre | 1.0.0 | |
148
- | HtmlBlock | 1.0.0 | |
149
- | TreeView | 1.0.0 | |
150
- | SlideShow | 1.0.0 | |
151
- | TextEditor | 1.0.0 | |
152
- | FieldSet | 1.0.0 | |
153
- | Popups (Alert, Confirm, Prompt) | 1.0.0 | |
154
- | Forms | 1.0.0 | |
155
-
156
- ### В разработке
157
- | Компонент | Версия | Описание |
158
- |-------------------------|--------|----------|
159
- | VideoPlayer | dev | - |
160
- | AudioPlayer | dev | - |
161
- | Graphs (Bar, Pie) | dev | - |
162
- | Дочерние BrowserWindows | dev | - |
1
+ # chuijs
2
+ ### CHROME_SANDBOX FIX
3
+ ```shell
4
+ cd node_modules/electron/dist && sudo chown root chrome-sandbox && sudo chmod 4755 chrome-sandbox
5
+ ```
6
+ ### Структура
7
+ #### exampleApp / app / views / main.js
8
+ ```javascript
9
+ const { Page, Button, TextInput, Notification, Log } = require('chuijs');
10
+ class MainPage extends Page {
11
+ constructor() {
12
+ super();
13
+ this.setTitle('Сказать привет!');
14
+ this.setMain(false)
15
+
16
+ let name = new TextInput({
17
+ title: 'Введите ваше имя',
18
+ placeholder: 'Введите ваше имя',
19
+ required: false
20
+ });
21
+ let hello = new Button({
22
+ title: "Сказать привет!",
23
+ clickEvent: () => {
24
+ if (name.getValue() !== "") {
25
+ new Notification({
26
+ title: `Привет, ${name.getValue()}!`, text: `Привет, ${name.getValue()}!`,
27
+ style: Notification.STYLE.SUCCESS, showTime: 5000
28
+ }).show();
29
+ } else {
30
+ new Notification({
31
+ title: `Привет мир!`, text: `Привет мир!`,
32
+ style: Notification.STYLE.SUCCESS, showTime: 5000
33
+ }).show();
34
+ }
35
+ }
36
+ })
37
+ this.add(name, hello)
38
+ }
39
+ }
40
+ exports.MainPage = MainPage
41
+ ```
42
+ #### exampleApp / app / app.js
43
+ ```javascript
44
+ /** RENDERER ПРОЦЕСС */
45
+ /** ИМПОРТЫ */
46
+ const { AppLayout, render, Log } = require('chuijs');
47
+ /** СТРАНИЦЫ */
48
+ const { MainPage } = require('../app/views/main');
49
+ class App extends AppLayout {
50
+ constructor() {
51
+ super();
52
+ /** РОУТЫ */
53
+ this.setRoute(new MainPage());
54
+ }
55
+ }
56
+ render(() => new App()).catch(err => Log.error(err))
57
+ ```
58
+ `render(callback)` возвращает `Promise`, который резолвится после `DOMContentLoaded` (или сразу, если документ уже загружен) результатом `callback` и реджектится его ошибкой.
59
+ #### exampleApp / main.js
60
+ ```javascript
61
+ /** main.js */
62
+ const { Main, MenuItem, path, App } = require('chuijs');
63
+ const main = new Main({
64
+ name: "exampleApp",
65
+ // размеры окна задаются вложенным объектом sizes
66
+ sizes: {
67
+ width: 1366,
68
+ height: 768,
69
+ minWidth: 960,
70
+ minHeight: 540
71
+ },
72
+ render: `${__dirname}/app/app.js`,
73
+ devTools: false,
74
+ resizable: true,
75
+ // папка загрузок (по умолчанию App.downloadsPath())
76
+ paths: {
77
+ downloadPath: path.join(App.userDataPath(), "downloads")
78
+ }
79
+ /** icon: `${__dirname}/resources/icons/app/icon.png` */
80
+ });
81
+ main.start({
82
+ hideOnClose: false,
83
+ tray: [
84
+ new MenuItem().help(`exampleApp`),
85
+ new MenuItem().button('Консоль разработчика', () => main.toggleDevTools()),
86
+ new MenuItem().separator(),
87
+ new MenuItem().quit("Выход")
88
+ ]
89
+ })
90
+ ```
91
+
92
+ > `Main` обычный класс, а не singleton: конструктор только сохраняет опции
93
+ > и настраивает `app.commandLine`. Окно создаётся в `start()` (внутри `app.whenReady()`),
94
+ > поэтому окно доступно через `main.getWindow()` только после старта.
95
+ #### Download Session в main.js
96
+ ```javascript
97
+ const { App } = require('chuijs');
98
+ App.get().on('session-created', (session) => {
99
+ session.on('will-download', (e, item, contents) => {
100
+ // ...
101
+ });
102
+ });
103
+ ```
104
+ #### TelegramBot API
105
+ ```javascript
106
+ const { TelegramBot } = require('chuijs');
107
+ let bot = new TelegramBot("token");
108
+ bot.setToken("token");
109
+
110
+ // Функции
111
+ async function run() {
112
+ await bot.getMe();
113
+ await bot.getUpdates();
114
+ await bot.getChat("id");
115
+ /* ... */
116
+ }
117
+ ```
118
+ ### Модули
119
+
120
+ Инфраструктура и утилиты из корня пакета: `const { Main, MenuItem, Log, path } = require('chuijs');`
121
+
122
+ #### `Main`
123
+ Создаёт окно, трей и управляет жизненным циклом приложения.
124
+
125
+ ```javascript
126
+ const { Main, MenuItem, App } = require('chuijs');
127
+ const main = new Main({
128
+ name: 'exampleApp',
129
+ sizes: { width: 1366, height: 768, minWidth: 960, minHeight: 540 },
130
+ render: `${__dirname}/app/app.js`,
131
+ devTools: false,
132
+ resizable: true,
133
+ paths: { downloadPath: path.join(App.userDataPath(), 'downloads') }
134
+ });
135
+ main.start({ hideOnClose: false, tray: [new MenuItem().quit('Выход')] });
136
+ ```
137
+
138
+ Опции конструктора: `name`, `sizes {width, height, minWidth, minHeight}`, `render`, `devTools`,
139
+ `webSecurity`, `resizable`, `paths {downloadPath}`, `icon`. Если `icon` не передан, `Main` сам
140
+ подставляет иконку приложения по умолчанию.
141
+ `start()` принимает `{hideOnClose, globalMenu, tray, extensions}` и создаёт окно внутри `app.whenReady()`.
142
+ API: `getWindow()`, `toggleDevTools()`, `stop()`, `restart()`, `enableAutoUpdateApp(interval)`,
143
+ `sendDownload(title, body)`, `sendDownloadUpdate(title, body)`, `sendDownloadComplete(title, body)`,
144
+ `sendDownloadError(title, body)`.
145
+
146
+ #### `App`
147
+ Обёртка над `app` Electron.
148
+
149
+ ```javascript
150
+ const { App } = require('chuijs');
151
+ App.get(); // app
152
+ App.getSession(); // session
153
+ App.getWebContents(); // webContents
154
+ ```
155
+
156
+ #### `App.*Path()`
157
+ Статические системные пути (все возвращают строку).
158
+
159
+ ```javascript
160
+ const { App } = require('chuijs');
161
+ App.userDataPath(); // .../AppData/<name>
162
+ App.downloadsPath();
163
+ App.logsPath();
164
+ ```
165
+
166
+ Полный список: `homePath()`, `appDataPath()`, `userDataPath()`, `sessionDataPath()`, `logsPath()`, `tempPath()`, `exePath()`, `modulePath()`, `desktopPath()`, `documentsPath()`, `downloadsPath()`, `musicPath()`, `picturesPath()`, `videosPath()`, `recentPath()`, `crashDumpsPath()`.
167
+
168
+ #### `Log`
169
+ Пишет в `userData/logs/app_<date>.log`.
170
+
171
+ ```javascript
172
+ Log.info('сообщение');
173
+ Log.error(new Error('что-то пошло не так'));
174
+ ```
175
+
176
+ Из renderer-процесса вызов уходит в main через `ipcRenderer.send("SEND_LOG_TEXT", ...)`.
177
+
178
+ #### `sleep`, `render`, `formatBytes`
179
+ Утилиты из `chui_functions`.
180
+
181
+ ```javascript
182
+ await sleep(1000); // пауза, мс
183
+ render(() => new App()).catch(err => Log.error(err)); // точка входа renderer
184
+ formatBytes(1536); // "1.5 KiB"
185
+ ```
186
+
187
+ `render(callback)` ждёт `DOMContentLoaded` и резолвится результатом `callback`.
188
+
189
+ #### Иконка приложения по умолчанию
190
+ `getDefaultIcon()` объявлена во внутреннем модуле `framework/modules/chui_functions` и
191
+ **наружу не реэкспортируется**: `require('chuijs').getDefaultIcon` — это `undefined`.
192
+ `Main` вызывает её сам, если опция `icon` не задана (на macOS результат дополнительно
193
+ прогоняется через `Main.#resizeIconTray()`). Свой путь задаётся опцией `icon`:
194
+
195
+ ```javascript
196
+ new Main({ name: 'app', icon: `${__dirname}/resources/icon.png` });
197
+ ```
198
+
199
+ #### `MenuItem`
200
+ Пункты меню трея и глобального меню (цепочка из 45 методов: `button`, `submenu`, `radio`,
201
+ `checkbox`, `help`, `quit`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `windowMenu` и др.).
202
+
203
+ ```javascript
204
+ new MenuItem().help('exampleApp');
205
+ new MenuItem().button('Консоль разработчика', () => main.toggleDevTools());
206
+ new MenuItem().separator();
207
+ new MenuItem().submenu('Ещё', [new MenuItem().quit('Выход')]);
208
+ new MenuItem().quit('Выход');
209
+ ```
210
+
211
+ #### `TelegramBot`
212
+ Обёртка над Telegram Bot HTTP API (26 методов).
213
+
214
+ ```javascript
215
+ const bot = new TelegramBot('token');
216
+ bot.setToken('token');
217
+ await bot.getMe();
218
+ await bot.sendMessage({ chat_id: '123', text: 'привет' });
219
+ ```
220
+
221
+ #### Реэкспорт Electron и зависимостей
222
+ Доступны прямо из пакета, без отдельного `require`:
223
+
224
+ ```javascript
225
+ const { BrowserWindow, ipcMain, ipcRenderer, shell, path, fs, os, request,
226
+ transliterate, systemPreferences, chooseFile, store, downloadSession } = require('chuijs');
227
+ ```
228
+
229
+ `chooseFile` — это `dialog` из `@electron/remote/main`, `store` — готовый экземпляр `new Store()`.
230
+
231
+ #### Автоматическое обновление приложения
232
+ Только AppImage и NSIS.
233
+
234
+ ```javascript
235
+ main.enableAutoUpdateApp(5000);
236
+ main.sendDownload('Обновление', 'Загрузка...');
237
+ main.sendDownloadComplete('Обновление', 'Готово');
238
+ main.sendDownloadError('Обновление', 'Ошибка');
239
+ ```
240
+
241
+ #### Themes
242
+ Оформление зависит от системной темы (светлая/тёмная), переключателя в API нет.
243
+
244
+ ### Компоненты
245
+
246
+ Все 57 классов-компонентов из `require('chuijs')` (таблица иконок `Icons`, константы `Styles` и
247
+ `AcceptTypes` описаны рядом отдельно). Любой компонент реализует `set()` — возвращает готовый
248
+ DOM-элемент, поэтому внутри `Page.add(...)`, `Card.add(...)`, `ContentBlock.add(...)` передаётся
249
+ сам объект, а в `Form.components` / `SlideShow.SLIDE` / `FieldSet.components` — тоже объекты.
250
+
251
+ #### Компоновка и навигация
252
+
253
+ ##### `AppLayout`
254
+ Корневой layout: шапка, app-меню, панель уведомлений. От него наследуется класс приложения.
255
+
256
+ ```javascript
257
+ class App extends AppLayout {
258
+ constructor() {
259
+ super();
260
+ this.setRoute(new MainPage());
261
+ }
262
+ }
263
+ ```
264
+
265
+ API: `setRoute(page)`, `setWindowControlsLeft()`, `setSearchToAppMenu()`, `setAutoCloseRouteMenu()`,
266
+ `addToHeaderRight([])`, `removeToHeaderRight([])`, `addToHeaderLeft([])`, `addToHeaderLeftBeforeTitle([])`,
267
+ `disableAppMenu()`, `setScript(pathToJS, id)`.
268
+ Статические фабрики: `AppLayout.TABS({width, default, tabs})`, `AppLayout.BUTTON({title, icon, reverse, clickEvent})`,
269
+ `AppLayout.USER_PROFILE({username, image: {noImage, imageLink, imageBase64}, items})`,
270
+ `AppLayout.USER_PROFILE_ITEM({title, icon, clickEvent})`,
271
+ `AppLayout.DIALOG({title, icon, reverse, dialogOptions: {title, closeOutSideClick, style, components}})`.
272
+
273
+ ##### `Route`
274
+ Переключатель страниц: `Route extends Events` (сам `Events` из пакета не экспортируется).
275
+ `go(page)` очищает центр окна, вставляет `page.render()` и рассылает событие
276
+ `route_event_<ИмяКласса>`. `Page` потомком `Route` не является — `new Route()` создаётся внутри
277
+ `Page.setBackButton()`.
278
+
279
+ ```javascript
280
+ this.setRoute(new MainPage()); // AppLayout: регистрирует страницу в app-меню
281
+ new Route().go(new MainPage()); // переключить центр окна на страницу
282
+ ```
283
+
284
+ ##### `Page`
285
+ Страница-контейнер: то, что попадает в окно через `setRoute`.
286
+
287
+ ```javascript
288
+ class MainPage extends Page {
289
+ constructor() {
290
+ super();
291
+ this.setTitle('Главная');
292
+ this.setMain(false);
293
+ this.add(new Button({ title: 'Кнопка' }));
294
+ }
295
+ }
296
+ ```
297
+
298
+ API: `add(...components)`, `remove(...components)`, `setBackButton({title, icon, reverse, page})`,
299
+ `setMenuBar(menuBar)`, `getMenuBar()`, `addRouteEvent(self, event)`, `setIcon/setTitle/setMain`,
300
+ `getIcon/getTitle/getMain`, `setFullHeight()`, `setFullWidth()`, `disablePadding()`,
301
+ `getMediaStream({video, audio})`, `render()`.
302
+
303
+ ##### `MenuBar`
304
+ Строка меню окна (пункты и выпадающие списки).
305
+
306
+ ```javascript
307
+ const menuBar = new MenuBar({ test: true });
308
+ menuBar.addMenuItems(
309
+ new Button({ icon: Icons.NAVIGATION.MENU, clickEvent: () => {} }),
310
+ MenuBar.DROPDOWN({ title: 'Ещё', items: [new Button({ title: 'О программе' })] })
311
+ );
312
+ page.setMenuBar(menuBar);
313
+ ```
314
+
315
+ Опции: `{test}`. API: `addMenuItems(...components)`.
316
+ Фабрика: `MenuBar.DROPDOWN({title, items})`.
317
+
318
+ ##### `ContextMenu`
319
+ Контекстное меню для любого элемента.
320
+
321
+ ```javascript
322
+ const menu = new ContextMenu({
323
+ items: [
324
+ ContextMenu.Item({ title: 'Открыть', icon: Icons.NAVIGATION.APPS, clickEvent: () => {} }),
325
+ ContextMenu.Separator(),
326
+ ContextMenu.SubMenu({ title: 'Ещё', items: [ContextMenu.Item({ title: 'Свойство' })] })
327
+ ]
328
+ });
329
+ menu.attach(table); // table — компонент или DOM-элемент
330
+ ```
331
+
332
+ Опции: `{items: []}`. API: `attach(target)`, `detach(target)`, `show(x, y)`, `hide()`, `destroy()`.
333
+ Фабрики: `ContextMenu.Item({title, icon, shortcut, disabled, clickEvent})`, `ContextMenu.Separator()`,
334
+ `ContextMenu.SubMenu({title, icon, items})`.
335
+
336
+ ##### `Styles`
337
+ Константы CSS-значений для опций `width`, `style` и объектов `styles`.
338
+
339
+ ```javascript
340
+ new ContentBlock({ direction: Styles.DIRECTION.COLUMN, justify: Styles.JUSTIFY.SPACE_BEETWEEN });
341
+ Styles.SIZE.WEBKIT_FILL; // "-webkit-fill-available"
342
+ ```
343
+
344
+ Группы: `WORD_BREAK`, `TEXT_ALIGN`, `ALIGN`, `JUSTIFY` (в `.JUSTIFY` опечатка — `SPACE_BEETWEEN`,
345
+ в коде именно так), `DIRECTION`, `WRAP`, `SIZE`.
346
+
347
+ ##### `Icon`
348
+ Отдельная иконка Material Symbols.
349
+
350
+ ```javascript
351
+ new Icon(Icons.NAVIGATION.CLOSE, '16px', 'red');
352
+ el.innerHTML = new Icon(Icons.CONTENT.ADD, '20px').getHTML();
353
+ ```
354
+
355
+ Сигнатура: `new Icon(name, size, color)`. API: `getHTML()`, `set()`.
356
+
357
+ ##### `Icons`
358
+ Справочник имён иконок — 18 групп: `NAVIGATION`, `TOGGLE`, `SOCIAL`, `SEARCH`, `PLACES`,
359
+ `NOTIFICATION`, `MAPS`, `IMAGE`, `HOME`, `HARDWARE`, `FILE`, `EDITOR`, `DEVICE`, `CONTENT`,
360
+ `ALERT`, `AUDIO_VIDEO`, `COMMUNICATION`, `ACTIONS`.
361
+
362
+ ```javascript
363
+ Icons.ACTIONS.HOME;
364
+ Icons.CONTENT.UNDO;
365
+ Icons.NAVIGATION.ARROW_BACK;
366
+ ```
367
+
368
+ #### Поля ввода
369
+
370
+ ##### `TextInput`
371
+ Однострочное текстовое поле.
372
+
373
+ ```javascript
374
+ const input = new TextInput({
375
+ name: 'name', title: 'Имя', placeholder: 'Введите имя', required: true
376
+ });
377
+ input.setValue('Пётр');
378
+ ```
379
+
380
+ Опции: `name`, `title`, `placeholder`, `width`, `required`, `value`, `disableFocus`,
381
+ `inputListener`, `focusListener`, `blurListener`.
382
+ API: `getName()`, `getTitle()`, `getValue()`, `setValue()`, `setDisabled()`, `setErrorMessage()`,
383
+ `addInputListener()`, `addFocusListener()`, `addBlurListener()`, `set()`.
384
+
385
+ ##### `TextArea`
386
+ Многострочное текстовое поле.
387
+
388
+ ```javascript
389
+ const area = new TextArea({ name: 'text', title: 'Сообщение', width: '300px', required: true });
390
+ ```
391
+
392
+ Опции: `name`, `title`, `placeholder`, `width`, `height`, `required`.
393
+ API: `getName()`, `getTitle()`, `getValue()`, `setValue()`, `setDisabled()`, `set()`.
394
+
395
+ ##### `PasswordInput`
396
+ Поле пароля.
397
+
398
+ ```javascript
399
+ const pass = new PasswordInput({ name: 'pass', title: 'Пароль', required: true });
400
+ pass.setErrorMessage('Слишком короткий пароль');
401
+ ```
402
+
403
+ Опции: `name`, `title`, `placeholder`, `width`, `required`.
404
+ API: `getName()`, `getTitle()`, `getValue()`, `setValue()`, `setDisabled()`, `setErrorMessage()`, `set()`.
405
+
406
+ ##### `EmailInput`
407
+ Поле e-mail.
408
+
409
+ ```javascript
410
+ const email = new EmailInput({ name: 'email', title: 'E-mail', placeholder: 'mail@example.com' });
411
+ ```
412
+
413
+ Опции: `name`, `title`, `placeholder`, `width`, `required`.
414
+ API: `getName()`, `getTitle()`, `getValue()`, `setValue()`, `setDisabled()`, `set()`.
415
+
416
+ ##### `NumberInput`
417
+ Числовое поле.
418
+
419
+ ```javascript
420
+ const count = new NumberInput({ name: 'count', title: 'Количество', width: '120px' });
421
+ count.setValue(3);
422
+ ```
423
+
424
+ Опции: `name`, `title`, `required`, `width`.
425
+ API: `getName()`, `getTitle()`, `getValue()`, `setValue()`, `setDisabled()`, `set()`.
426
+
427
+ ##### `DateInput`
428
+ Поле даты со встроенным календарём.
429
+
430
+ ```javascript
431
+ const date = new DateInput({ name: 'date', title: 'Дата' });
432
+ date.getValue(); // строка из input
433
+ ```
434
+
435
+ Опции: `name`, `title`, `required`.
436
+ API: `getName()`, `getTitle()`, `getValue()`, `setValue()`, `setDisabled()`, `set()`.
437
+
438
+ ##### `CheckBox`
439
+ Флажок.
440
+
441
+ ```javascript
442
+ const box = new CheckBox({ name: 'agree', title: 'Согласен', changeListener: () => {} });
443
+ box.setValue(true);
444
+ ```
445
+
446
+ Опции: `name`, `title`, `required`, `changeListener`.
447
+ API: `getName()`, `getTitle()`, `getValue()`, `setValue()`, `addChangeListener()`, `setDisabled()`, `set()`.
448
+
449
+ ##### `Toggle`
450
+ Переключатель без подписи.
451
+
452
+ ```javascript
453
+ const toggle = new Toggle();
454
+ toggle.setValue(true);
455
+ toggle.setMargin({ top: '8px', bottom: '8px' });
456
+ ```
457
+
458
+ Конструктор без опций. API: `getValue()`, `setValue(boolean)`, `setId(id)`,
459
+ `setMargin({top, bottom, left, right})`, `addChangeListener(fn)`, `set()`.
460
+
461
+ ##### `RadioButton`
462
+ Одна радиокнопка.
463
+
464
+ ```javascript
465
+ const radio = new RadioButton({ name: 'mode', title: 'Вариант', value: 'a' });
466
+ ```
467
+
468
+ Опции: `name`, `title`, `stringValue`, `value`, `required`, `width`.
469
+ API: `getName()`, `getValue()`, `setValue()`, `addChangeListener()`, `setDisabled()`, `set()`.
470
+
471
+ ##### `RadioGroup`
472
+ Группа радиокнопок, собирается из массива опций.
473
+
474
+ ```javascript
475
+ const group = new RadioGroup({ styles: { direction: Styles.DIRECTION.COLUMN } });
476
+ group.addOptions([{ name: 'a', value: 'Вариант A' }, { name: 'b', value: 'Вариант B' }]);
477
+ group.addChangeListener(() => {});
478
+ group.getValue(); // бросает исключение, если ничего не выбрано
479
+ ```
480
+
481
+ Опции: `styles {direction, wrap, align, justify, width}`.
482
+ API: `addOptions([{name, value}])`, `addChangeListener(fn)`, `clear()`, `getValue()`, `set()`.
483
+
484
+ ##### `Select`
485
+ Одиночный выпадающий список.
486
+
487
+ ```javascript
488
+ const select = new Select({ name: 'city', title: 'Город', required: true });
489
+ select.addOptions({ title: 'Москва', value: 'msk' }, { title: 'Питер', value: 'spb' });
490
+ select.setDefaultOption({ value: 'msk' });
491
+ select.addValueChangeListener(() => {});
492
+ ```
493
+
494
+ Опции: `name`, `title`, `placeholder`, `width`, `required`.
495
+ API: `addOption(title, value)`, `addOptions(...options)`, `setDefaultOption(object)`, `setDropdownHeight(height)`,
496
+ `addValueChangeListener(fn)`, `getName()`, `getValue()`, `setDisabled()`, `setBottonOpenVisible(visible)`
497
+ (имя метода с опечаткой — именно `Botton`), `set()`.
498
+
499
+ ##### `ComboBox`
500
+ Список с поиском по вводу.
501
+
502
+ ```javascript
503
+ const combo = new ComboBox({ name: 'item', title: 'Поиск', width: '300px', optionsLen: 5 });
504
+ combo.addOptions({ title: 'Пункт', value: '1' });
505
+ combo.addValueChangeListener(() => {});
506
+ combo.getValue();
507
+ ```
508
+
509
+ Опции: `name`, `title`, `placeholder`, `width`, `required`, `optionsLen`.
510
+ API: `addOptions(...options)`, `addValueChangeListener(fn)`, `getName()`, `getValue()`, `setDisabled()`, `clear()`, `set()`.
511
+
512
+ ##### `MultiComboBox`
513
+ Множественный выбор с секциями.
514
+
515
+ ```javascript
516
+ const multi = new MultiComboBox({ name: 'tags', title: 'Теги', width: '300px' });
517
+ multi.addOptionsWithSections([{ title: 'Секция', options: [{ title: 'Пункт', value: '1' }] }]);
518
+ multi.getValue(); // массив выбранных значений
519
+ ```
520
+
521
+ Опции: `name`, `title`, `placeholder`, `width`, `required`, `optionsLen`.
522
+ API: `addOptions(...options)`, `addOptionsWithSections([{title, options}])`, `clearDropdown()`,
523
+ `addValueChangeListener(fn)`, `getName()`, `getValue()`, `setDisabled()`, `clear()`, `set()`.
524
+
525
+ ##### `FileInput`
526
+ Выбор файлов с диска.
527
+
528
+ ```javascript
529
+ const file = new FileInput({
530
+ name: 'file', title: 'Изображение', accept: [AcceptTypes.ALL_IMAGE],
531
+ multiple: false, changeListener: () => {}
532
+ });
533
+ file.getFiles();
534
+ ```
535
+
536
+ Опции: `name`, `title`, `accept`, `required`, `disableFocus`, `multiple`, `changeListener`, `focusListener`, `blurListener`.
537
+ API: `getFile(index)`, `getFiles()`, `getName()`, `getTitle()`, `setDisabled()`, `addChangeListener()`,
538
+ `addFocusListener()`, `addBlurListener()`, `set()`.
539
+
540
+ ##### `AcceptTypes`
541
+ Константа MIME-типов для опции `accept` у `FileInput` (не компонент, а набор строк).
542
+
543
+ ```javascript
544
+ AcceptTypes.ALL_IMAGE; // 'image/*'
545
+ AcceptTypes.PNG; // 'image/png'
546
+ AcceptTypes.GIF; // 'image/gif'
547
+ AcceptTypes.JPEG; // 'image/jpeg'
548
+ AcceptTypes.ALL_AUDIO; // 'audio/*'
549
+ AcceptTypes.ALL_VIDEO; // 'video/*'
550
+ ```
551
+
552
+ ##### `Form`
553
+ HTML-форма с отправкой и перехватом `submit`.
554
+
555
+ ```javascript
556
+ const form = new Form({
557
+ method: Form.METHOD.POST,
558
+ components: [
559
+ new TextInput({ name: 'name', title: 'Имя', required: true }),
560
+ Form.SubmitButton('Отправить')
561
+ ],
562
+ submitEvent: (e) => {
563
+ e.preventDefault();
564
+ new Notification({ title: 'Form', text: 'Отправлено', showTime: 3000 }).show();
565
+ }
566
+ });
567
+ ```
568
+
569
+ Опции: `action`, `method`, `components`, `submitEvent`.
570
+ API: `set()`, статики `Form.SubmitButton(title)` и `Form.METHOD.{GET, POST}`.
571
+
572
+ ##### `FieldSet`
573
+ Группа полей с легендой.
574
+
575
+ ```javascript
576
+ const set = new FieldSet({
577
+ id: 'f1', name: 'group', title: 'Данные',
578
+ style: { direction: Styles.DIRECTION.COLUMN, width: Styles.SIZE.WEBKIT_FILL },
579
+ components: [new TextInput({ name: 'name', title: 'Имя' })]
580
+ });
581
+ set.add(new Button({ title: 'Готово' }));
582
+ ```
583
+
584
+ Опции: `id`, `name`, `title`, `style {width, height, direction, wrap, align, justify}`, `components`.
585
+ API: `getId()`, `getLegend()`, `setLegend(text)`, `add(...components)`, `set()`.
586
+
587
+ #### Отображение
588
+
589
+ ##### `Button`
590
+ Кнопка с текстом и/или иконкой.
591
+
592
+ ```javascript
593
+ const button = new Button({ title: 'Нажать', icon: Icons.CONTENT.ADD, clickEvent: () => {} });
594
+ button.setDisabled(true);
595
+ ```
596
+
597
+ Опции: `reverse`, `title`, `icon`, `clickEvent`.
598
+ API: `getText()`, `setText()`, `addClickListener()`, `setDisabled(boolean)`, `set()`.
599
+
600
+ > `disabled` в конструкторе не читается — состояние задаётся только через `setDisabled(true)`.
601
+
602
+ ##### `H`
603
+ Заголовок `H1`…`H6`.
604
+
605
+ ```javascript
606
+ new H(1, 'Заголовок');
607
+ new H(3, 'Подзаголовок').setText('Другой текст');
608
+ ```
609
+
610
+ Сигнатура: `new H(num, text)`. API: `setText()`, `set()`.
611
+
612
+ ##### `Label`
613
+ Подпись с обычным или markdown-текстом.
614
+
615
+ ```javascript
616
+ const label = new Label({ text: 'Подпись', fontSize: '14px' });
617
+ const md = new Label({ markdownText: '**Жирный** текст' });
618
+ ```
619
+
620
+ Опции: `id`, `text`, `markdownText` (взаимоисключающие), `textAlign`, `wordBreak`, `width`, `fontSize`.
621
+ API: `getId()`, `getText()`, `getMarkdownText()`, `setId()`, `setText()`, `setMarkdownText()`, `set()`.
622
+
623
+ ##### `Paragraph`
624
+ Абзац текста.
625
+
626
+ ```javascript
627
+ new Paragraph('Текст абзаца');
628
+ ```
629
+
630
+ Сигнатура: `new Paragraph(text)`. API: `setText()`, `set()`.
631
+
632
+ ##### `Badge`
633
+ Небольшой цветной бейдж.
634
+
635
+ ```javascript
636
+ new Badge({ text: 'ERROR', style: Badge.STYLE.ERROR });
637
+ new Badge({ markdownText: '**OK**', style: Badge.STYLE.SUCCESS });
638
+ ```
639
+
640
+ Опции: `id`, `text`, `markdownText`, `style`.
641
+ API: `getId()`, `getText()`, `getMarkdownText()`, `setId()`, `setText()`, `setMarkdownText()`, `setStyle()`, `set()`.
642
+ Константа: `Badge.STYLE.{ERROR, SUCCESS, WARNING}`.
643
+
644
+ ##### `Spinner`
645
+ Индикатор загрузки.
646
+
647
+ ```javascript
648
+ const spinner = new Spinner(Spinner.SIZE.DEFAULT, 'auto');
649
+ spinner.remove();
650
+ ```
651
+
652
+ Сигнатура: `new Spinner(size, margin)`. API: `remove()`, `set()`.
653
+ Константа: `Spinner.SIZE.{V_SMALL, SMALL, DEFAULT, BIG, V_BIG}`, у `V_SMALL` — `{SIZE: 30, BORDER: 3}`.
654
+
655
+ ##### `ProgressBar`
656
+ Полоса прогресса с подписями.
657
+
658
+ ```javascript
659
+ const progress = new ProgressBar({ max: 100 });
660
+ progress.setValue(40);
661
+ progress.setProgressText('40%');
662
+ ```
663
+
664
+ Опции: `{max}`.
665
+ API: `setMax()`, `setWidth()`, `setValue()`, `setProgressText()`, `setProgressCountText()`, `set()`.
666
+
667
+ ##### `ContentBlock`
668
+ Flex-контейнер для произвольной компоновки.
669
+
670
+ ```javascript
671
+ const block = new ContentBlock({
672
+ direction: Styles.DIRECTION.COLUMN, align: Styles.ALIGN.START, justify: Styles.JUSTIFY.START
673
+ });
674
+ block.add(new Paragraph('Строка'), new Button({ title: 'Кнопка' }));
675
+ ```
676
+
677
+ Опции: `display`, `direction`, `wrap`, `align`, `justify`, `disableMarginChild`.
678
+ API: `add(...components)`, `remove(...components)`, `clear()`, `disableMarginChild()`, `setAutoOverflow()`,
679
+ `setContentEditable()`, `setWidth()`, `setHeight()`, `setPadding()`, `set()`.
680
+
681
+ ##### `Card`
682
+ Карточка с шапкой (иконка, заголовок, описание) и телом. Внутри витрины `app/views/0_maket.js`.
683
+
684
+ ```javascript
685
+ const card = new Card({ id: 'c1', title: 'Заголовок', description: 'Пояснение', icon: Icons.CONTENT.ADD });
686
+ card.add(new Paragraph('Содержимое'));
687
+ card.getBody(); // <card_body>
688
+ ```
689
+
690
+ Опции: `id`, `title`, `description`, `icon`, `width`, `height`, `style`, `components`.
691
+ API: `getId()`, `getTitle()`, `getDescription()`, `getBody()`, `setId()`, `setTitle()`, `setDescription()`,
692
+ `setIcon()`, `setWidth()`, `setHeight()`, `setStyle()`, `add(...components)`, `clear()`, `set()`.
693
+
694
+ ##### `Hero`
695
+ Шапка страницы с иконкой, текстом и статистикой.
696
+
697
+ ```javascript
698
+ new Hero({
699
+ title: 'chUiJS', description: 'Витрина компонентов', icon: Icons.HOME.SENSOR_DOOR,
700
+ stats: [{ value: '56', label: 'КОМПОНЕНТОВ' }]
701
+ });
702
+ ```
703
+
704
+ Опции: `id`, `title`, `description`, `icon`, `stats: [{value, label}]`, `style`, `width`, `height`.
705
+ API: `getId()`, `getTitle()`, `getDescription()`, `setId()`, `setTitle()`, `setDescription()`, `setIcon()`,
706
+ `setWidth()`, `setHeight()`, `setStyle()`, `addStat(value, label)`, `clearStats()`, `set()`.
707
+
708
+ ##### `Details`
709
+ Раскрывающийся блок.
710
+
711
+ ```javascript
712
+ const details = new Details({ title: 'Подробнее', width: Styles.SIZE.WEBKIT_FILL });
713
+ details.add(new Paragraph('Скрытый текст'));
714
+ ```
715
+
716
+ Опции: `title`, `contenteditable`, `direction`, `wrap`, `align`, `justify`, `width`.
717
+ API: `add(...components)`, `set()`.
718
+
719
+ ##### `Accordion`
720
+ Аккордеон из секций; на вход идёт **массив**, а не объект.
721
+
722
+ ```javascript
723
+ new Accordion([{ b_text: 'Раздел 1', p_text: 'Текст раздела 1' }]);
724
+ ```
725
+
726
+ ##### `CodeBlock`
727
+ Блок с предформатированным текстом (команды, код).
728
+
729
+ ```javascript
730
+ new CodeBlock('npm run start', { width: Styles.SIZE.MAX_CONTENT });
731
+ ```
732
+
733
+ Сигнатура: `new CodeBlock(text, options {textAlign, wordBreak, width})`. API: `setText()`, `set()`.
734
+
735
+ ##### `HtmlBlock`
736
+ Произвольный HTML внутри layout.
737
+
738
+ ```javascript
739
+ const html = new HtmlBlock(Styles.SIZE.WEBKIT_FILL);
740
+ html.setHtml('<b>жирный</b> и <i>курсив</i>');
741
+ ```
742
+
743
+ Сигнатура: `new HtmlBlock(width)`. API: `setHtml(html)`, `set()`.
744
+
745
+ ##### `Table`
746
+ Таблица по массиву объектов (ключи объекта — колонки).
747
+
748
+ ```javascript
749
+ class Car { constructor(car, model, size) { this.car = car; this.model = model; this.size = size; } }
750
+ const table = new Table({ data: [new Car('Acura', 'NSX', 10)], sorted: true });
751
+ table.setFilterByProperty(Table.FILTER_TYPE.PARTIAL_MATCH, 'model', 'n');
752
+ table.setContextMenu(menu);
753
+ ```
754
+
755
+ Опции: `data`, `customName`, `userSelect`, `sorted`, `columnsWidth`.
756
+ API: `setData()`, `getData()`, `refresh(data)`, `getColumn(col)`, `setContextMenu(ctx)`,
757
+ `setFilterByProperty(type, property, filterValue)`, `setFilterByMultiProperty(type, properties, filterValue)`,
758
+ `removeFilterByProperty(property, filterValue)`, `set()`.
759
+ Константа: `Table.FILTER_TYPE.{CLEAR_MATCH, PARTIAL_MATCH}`.
760
+
761
+ ##### `TreeView`
762
+ Дерево из кнопок и раскрывающихся узлов.
763
+
764
+ ```javascript
765
+ new TreeView({
766
+ width: Styles.SIZE.WEBKIT_FILL,
767
+ components: [
768
+ TreeView.Button({ title: 'Кнопка', listener: () => {} }),
769
+ TreeView.ExpandButton({ title: 'Папка', subButtons: [], components: [new Paragraph('Внутри')] })
770
+ ]
771
+ });
772
+ ```
773
+
774
+ Опции: `width`, `components`.
775
+ Фабрики: `TreeView.Button({title, listener})`, `TreeView.ExpandButton({title, subButtons, components})`.
776
+
777
+ ##### `Tabs` и `Tab`
778
+ Вкладки: `Tab` создаётся отдельно, `Tabs` собирает их в панель.
779
+
780
+ ```javascript
781
+ const first = new Tab('Вкладка 1');
782
+ first.addContent(new TextInput({ title: 'Поле 1', width: '300px' }));
783
+ new Tabs({ default: 0, width: Styles.SIZE.WEBKIT_FILL, tabs: [first, new Tab('Вкладка 2')] });
784
+ ```
785
+
786
+ `Tab(title)`: `addContent(...contents)`, `getTab()`, `getContent()`, `set()`.
787
+ `Tabs({width, default, tabs})`: `set()`.
788
+
789
+ ##### `BarGraph`
790
+ Столбчатая диаграмма (клик по столбцу показывает `Notification`).
791
+
792
+ ```javascript
793
+ new BarGraph({ data: { 'Пн': 4, 'Вт': 7 }, colors: ['#0a84ff'] });
794
+ ```
795
+
796
+ ##### `PieGraph`
797
+ Круговая или кольцевая диаграмма.
798
+
799
+ ```javascript
800
+ new PieGraph({ data: { 'Да': 30, 'Нет': 70 }, colors: ['#34c759', '#ff3b30'], legend: true, doughnutHoleSize: 0.5 });
801
+ ```
802
+
803
+ #### Окна, уведомления, медиа
804
+
805
+ ##### `Dialog`
806
+ Модальное окно с шапкой, телом и подвалом.
807
+
808
+ ```javascript
809
+ const dialog = new Dialog({ width: '420px', height: '260px', closeOutSideClick: true });
810
+ dialog.addToHeader(new Paragraph('Заголовок'));
811
+ dialog.addToBody(new Paragraph('Содержимое'));
812
+ dialog.addToFooter(new Button({ title: 'Понятно', clickEvent: () => dialog.close() }));
813
+ dialog.open();
814
+ ```
815
+
816
+ Опции: `width` (по умолчанию `max-content`), `height`, `closeOutSideClick`.
817
+ API: `addToHeader()`, `addToBody()`, `addToFooter()`, `removeFromHeader()`, `removeFromBody()`,
818
+ `removeFromFooter()`, `open()`, `openAndClose()`, `close()`, свойство `isOpen`, `set()`.
819
+
820
+ ##### `Popup`
821
+ Готовые системные попапы: `alert`, `confirm`, `prompt`.
822
+
823
+ ```javascript
824
+ const popup = new Popup();
825
+ popup.alert({ title: 'Внимание', message: 'Текст' });
826
+ const ok = await popup.confirm({ title: 'Удалить?', message: 'Действие необратимо' });
827
+ const data = await popup.prompt({ title: 'Ввод', message: 'Заполните поля',
828
+ inputs: { text: { placeholder: 'Логин', errorMessage: 'Обязательное поле' },
829
+ password: { placeholder: 'Пароль' } } });
830
+ ```
831
+
832
+ `alert` ничего не возвращает, `confirm` → `Promise<boolean>`,
833
+ `prompt` → `Promise<{text?, password?}>`. Классы `PopupAlert`, `PopupConfirm`, `PopupPrompt` наружу не экспортируются.
834
+
835
+ ##### `Notification`
836
+ Тост в панели уведомлений.
837
+
838
+ ```javascript
839
+ new Notification({
840
+ title: 'Готово', text: 'Файл сохранён',
841
+ style: Notification.STYLE.SUCCESS, showTime: 5000
842
+ }).show(true); // true — продублировать системным уведомлением
843
+ ```
844
+
845
+ Опции: `title` **или** `markdownTitle`, `text` **или** `markdownText`, `style`, `showTime`.
846
+ API: `show(showOnSystem)`. Константа: `Notification.STYLE.{ERROR, SUCCESS, WARNING}`.
847
+
848
+ ##### `UpdateNotification`
849
+ Уведомление о процессе обновления со спиннером.
850
+
851
+ ```javascript
852
+ const update = new UpdateNotification({ title: 'Обновление', text: 'Скачивание', spinner: true });
853
+ update.show(true);
854
+ update.hide();
855
+ ```
856
+
857
+ Опции: `{title, text, spinner}`. API: `show(showOnSystem)`, `hide()`.
858
+
859
+ ##### `DownloadNotification`
860
+ Уведомление о загрузке файла.
861
+
862
+ ```javascript
863
+ const download = new DownloadNotification({ title: 'Файл', text: 'Загрузка...', type: 'image' });
864
+ download.show();
865
+ download.update('Файл', 'Почти готово');
866
+ download.done(); // или download.error();
867
+ ```
868
+
869
+ Опции: `{title, text, type}`. API: `show()`, `update(title, text)`, `done()`, `error()`.
870
+
871
+ ##### `DownloadProgressNotification`
872
+ Уведомление о загрузке с прогрессом.
873
+
874
+ ```javascript
875
+ const progress = new DownloadProgressNotification({ title: 'Файл', text: 'Загрузка...', type: 'image' });
876
+ progress.show();
877
+ progress.update('Файл', 'Половина', 50, 100); // title, text, value, max
878
+ progress.done();
879
+ ```
880
+
881
+ Опции: `{title, text, type}` — `max` задаётся не в конструкторе, а в `update(title, text, value, max)`.
882
+ API: `show()`, `update(title, text, value, max)`, `done()`, `error()`.
883
+
884
+ ##### `Calendar`
885
+ Сетка месяца (месяц нумеруется с 1).
886
+
887
+ ```javascript
888
+ const calendar = new Calendar(2026, 9);
889
+ calendar.getCalendar();
890
+ ```
891
+
892
+ Сигнатура: `new Calendar(year, month)`. API: `getCalendar()`, `getDateNow()`, `getMonthName()`,
893
+ `getDayName()`, `getDate()`, `getMonth()`, `getYear()`. Метода `set()` нет — это не компонент-элемент.
894
+
895
+ ##### `SlideShow`
896
+ Слайдер; слайды описываются фабрикой `SLIDE`.
897
+
898
+ ```javascript
899
+ new SlideShow({
900
+ width: Styles.SIZE.WEBKIT_FILL, height: '260px',
901
+ autoplay: { status: true, interval: 4000 },
902
+ slides: [SlideShow.SLIDE({
903
+ size: { width: Styles.SIZE.WEBKIT_FILL, height: Styles.SIZE.WEBKIT_FILL },
904
+ style: { align: Styles.ALIGN.CENTER, justify: Styles.JUSTIFY.CENTER },
905
+ components: [new H(3, 'Слайд 1'), new Paragraph('Текст')]
906
+ })]
907
+ });
908
+ ```
909
+
910
+ Опции: `width`, `height`, `autoplay {status, interval}`, `slides`.
911
+ Фабрика: `SlideShow.SLIDE({size: {width, height}, style: {direction, wrap, align, justify}, components: [...]})`
912
+ (класс `Slide` не экспортируется).
913
+
914
+ > Подпись `SLIDE` обещает `{width, height}`, но `Slide` читает `options.size.width` и
915
+ > `options.style.*`: вызов `SlideShow.SLIDE({components: [...]})` без `size` и `style` падает
916
+ > с `TypeError`. Передавайте оба объекта, как в примере выше.
917
+
918
+ ##### `TextEditor`
919
+ WYSIWYG-редактор с панелью форматирования.
920
+
921
+ ```javascript
922
+ const editor = new TextEditor('100%', {
923
+ title: 'Описание',
924
+ controls: { BOLD: true, ITALIC: true, INSERT_LINK: true }
925
+ });
926
+ editor.setValueAsHTML('<b>текст</b>');
927
+ ```
928
+
929
+ Сигнатура: `new TextEditor(height, options {title, controls})`.
930
+ Ключи `controls` (20): `UNDO_REDO`, `BLOCK_FORMAT`, `FONT_SIZE`, `REMOVE_FORMAT`, `BOLD`, `ITALIC`,
931
+ `STRIKE_THROUGH`, `UNDERLINE`, `SUPERSCRIPT`, `SUBSCRIPT`, `JUSTIFY_LEFT`, `JUSTIFY_CENTER`,
932
+ `JUSTIFY_RIGHT`, `JUSTIFY_FULL`, `LISTS`, `CONTENT_CONTROLS`, `INSERT_LINK`, `INSERT_TABLE`,
933
+ `INSERT_IMAGE`, `LINE_BREAK`. Панель форматирования собирается классом `TextEditorPanel`,
934
+ который наружу не экспортируется.
935
+ API: `getValueAsHTML()`, `setValueAsHTML()`, `getValueAsText()`, `setValueAsText()`, `addInputListener()`, `set()`.
936
+
937
+ ##### `Image`
938
+ Изображение из файла, base64 или по HTTP.
939
+
940
+ ```javascript
941
+ new Image({ path: `${__dirname}/icon.png`, width: '64px', openPopup: true });
942
+ ```
943
+
944
+ Опции: `path`, `base64`, `httpPath`, `width`, `height`, `openPopup`, `disableMargin`.
945
+ API: `setWidth()`, `setHeight()`, `set()`.
946
+
947
+ ##### `Audio`
948
+ Аудиоплеер с плейлистом и эквалайзером.
949
+
950
+ ```javascript
951
+ const player = new Audio({ autoplay: false, pin: Audio.PIN.TOP, playlist: true, width: '400px' });
952
+ player.setPlayList([{ title: 'Трек', artist: 'Исполнитель', mimetype: 'audio/mpeg' }]);
953
+ player.setActive(0);
954
+ player.play();
955
+ player.openFolder('/music');
956
+ player.restoreFX();
957
+ ```
958
+
959
+ Опции: `autoplay`, `pin`, `playlist`, `width`, `height`.
960
+ API: `play()`, `next()`, `prev()`, `openFolder(path)`, `setPlayList(list)`, `setActive(index)`, `restoreFX()`, `set()`.
961
+ Константы: `Audio.PIN.{TOP, BOTTOM}` и `Audio.MIMETYPES` (`AU_SND`, `LINEAR_PCM`, `MID_RMI`, `MP3`,
962
+ `MP4`, `AIF_AIFC_AIFF`, `M3U`, `RA_RAM`, `OGG`, `VORBIS`, `WAV`; у `LINEAR_PCM` значение
963
+ `'auido/L24'` — опечатка уже в коде).
964
+
965
+ ##### `Video`
966
+ Видеоплеер; умеет проигрывать `MediaStream` (например, веб-камеру).
967
+
968
+ ```javascript
969
+ const video = new Video({ autoplay: false, width: Styles.SIZE.WEBKIT_FILL, height: 'auto' });
970
+ video.setStream(stream); // MediaStream из Page.getMediaStream({video: true})
971
+ video.setPlayList([{ title: 'Видео', mimetype: Video.MIMETYPES.MP4, videoPath: '/video.mp4' }]);
972
+ ```
973
+
974
+ Опции: `autoplay`, `width`, `height`.
975
+ API: `setStream(stream)`, `setPlayList(list)`, `getPlayList()`, `set()`.
976
+ Константа: `Video.MIMETYPES.{MP4, WEBM, M4V, QUICK_TIME}`.
977
+
978
+ ##### `WebView`
979
+ Встроенный тег `webview` со своим preload и партишеном.
980
+
981
+ ```javascript
982
+ const web = new WebView('https://example.com', false);
983
+ web.setPartition('persist:app');
984
+ web.setPreload(`${__dirname}/preload.js`);
985
+ web.insertCustomRes({ cssPath: `${__dirname}/inject.css`, jsPath: `${__dirname}/inject.js` });
986
+ web.addFinishLoadEvent(() => {});
987
+ ```
988
+
989
+ Сигнатура: `new WebView(url, dev)`. API: `setUrl()`, `setPartition()`, `setPreload()`,
990
+ `addStartLoadEvent()`, `addStopLoadEvent()`, `addFinishLoadEvent()`, `insertCustomRes({cssPath, jsPath})`,
991
+ `executeJavaScript(code)`, `send(channel, ...args)`, `getWebContentsId()`, `set()`.
992
+
993
+ ##### `Console`
994
+ Встроенная консоль для вывода текста.
995
+
996
+ ```javascript
997
+ const console_ = new Console({ width: '100%', height: '240px' });
998
+ console_.addText('$ npm run start');
999
+ ```
1000
+
1001
+ Опции: `{width, height}`. API: `addText(text)`, `set()`.
1002
+
1003
+ ##### `CustomElement`
1004
+ Обёртка над произвольным DOM-элементом.
1005
+
1006
+ ```javascript
1007
+ const el = new CustomElement({ tag: 'div', id: 'my-block', className: 'css_class', pathToCSS: `${__dirname}/style.css` });
1008
+ el.innerHTML('<b>HTML</b>');
1009
+ ```
1010
+
1011
+ Опции: `tag`, `id`, `className`, `pathToCSS` (`tag` и `id` обязательны — иначе исключение).
1012
+ API: `innerText()`, `innerHTML()`, `addEventListener(type, listener)`, `set()`.
1013
+
1014
+ #### Константы и статические фабрики
1015
+
1016
+ Кроме компонентов из `require('chuijs')` достаются константы и фабрики:
1017
+
1018
+ - **Размеры и раскладка** — `Styles.WORD_BREAK.*`, `Styles.TEXT_ALIGN.*`, `Styles.ALIGN.*`,
1019
+ `Styles.JUSTIFY.*`, `Styles.DIRECTION.*`, `Styles.WRAP.*`, `Styles.SIZE.*`.
1020
+ - **Стили уведомлений и бейджей** — `Notification.STYLE.{ERROR, SUCCESS, WARNING}`,
1021
+ `Badge.STYLE.{ERROR, SUCCESS, WARNING}`.
1022
+ - **Размеры спиннера** — `Spinner.SIZE.{V_SMALL, SMALL, DEFAULT, BIG, V_BIG}`.
1023
+ - **Фильтры таблицы** — `Table.FILTER_TYPE.{CLEAR_MATCH, PARTIAL_MATCH}`.
1024
+ - **Метод формы** — `Form.METHOD.{GET, POST}`.
1025
+ - **Типы файлов** — `AcceptTypes.{ALL_IMAGE, PNG, GIF, JPEG, ALL_AUDIO, ALL_VIDEO}`.
1026
+ - **Медиа** — `Audio.PIN.{TOP, BOTTOM}`, `Audio.MIMETYPES`, `Video.MIMETYPES.{MP4, WEBM, M4V, QUICK_TIME}`.
1027
+ - **Элементы шапки** — `AppLayout.TABS({width, default, tabs})`, `AppLayout.BUTTON({title, icon, reverse, clickEvent})`,
1028
+ `AppLayout.USER_PROFILE({username, image, items})`, `AppLayout.USER_PROFILE_ITEM({title, icon, clickEvent})`,
1029
+ `AppLayout.DIALOG({title, icon, reverse, dialogOptions})`.
1030
+ - **Меню, дерево, слайды** — `MenuBar.DROPDOWN({title, items})`, `ContextMenu.Item/Separator/SubMenu`,
1031
+ `TreeView.Button({title, listener})`, `TreeView.ExpandButton({title, subButtons, components})`,
1032
+ `Form.SubmitButton(title)`, `SlideShow.SLIDE({size, style, components})`.
1033
+
1034
+ > Не экспортируются из пакета (доступны только через фабрики): `Slide`, `SubmitButton`,
1035
+ > `TextEditorPanel`, `Commands`, а также классы popup-окон (`PopupAlert`, `PopupConfirm`, `PopupPrompt`).
1036
+ > Из `chui_functions` наружу не отдаются `getDate()`, `style_parse(json, component)`,
1037
+ > `markdownToHtml(text)`, `htmlToMarkdown(html)`, `shouldOpenDropdownUp(dropdown, anchor)`,
1038
+ > `setStyles(pathToCSS, component)` — они доступны только внутри фреймворка.
1039
+
1040
+ #### Сценарии целиком
1041
+
1042
+ ```javascript
1043
+ /** AppLayout + Page + шапка */
1044
+ const { AppLayout, Page, Icon, Icons, Button, TextInput, Notification, MenuBar, render, Log } = require('chuijs');
1045
+
1046
+ class MainPage extends Page {
1047
+ constructor() {
1048
+ super();
1049
+ this.setTitle('Главная');
1050
+ this.setIcon(Icons.ACTIONS.HOME);
1051
+ this.setMain(true); // домашний роут
1052
+ this.setFullWidth(); // растянуть по ширине окна
1053
+ const menuBar = new MenuBar({ test: true });
1054
+ menuBar.addMenuItems(
1055
+ new Button({ icon: Icons.NAVIGATION.MENU, clickEvent: () => {} }),
1056
+ MenuBar.DROPDOWN({ title: 'Ещё', items: [new Button({ title: 'О программе' })] })
1057
+ );
1058
+ this.setMenuBar(menuBar);
1059
+ const name = new TextInput({ title: 'Имя', placeholder: 'Имя' });
1060
+ this.add(name, new Button({ title: 'Поздороваться', clickEvent: () =>
1061
+ new Notification({ title: 'Привет!', text: name.getValue(), style: Notification.STYLE.SUCCESS, showTime: 4000 }).show()
1062
+ }));
1063
+ }
1064
+ }
1065
+
1066
+ class App extends AppLayout {
1067
+ constructor() {
1068
+ super();
1069
+ this.setRoute(new MainPage());
1070
+ this.addToHeaderLeft([AppLayout.BUTTON({ title: 'Назад', icon: Icons.NAVIGATION.ARROW_BACK, reverse: true, clickEvent: () => {} })]);
1071
+ this.addToHeaderRight([AppLayout.USER_PROFILE({
1072
+ username: 'user',
1073
+ items: [AppLayout.USER_PROFILE_ITEM({ title: 'Настройки', clickEvent: () => {} })]
1074
+ })]);
1075
+ }
1076
+ }
1077
+ render(() => new App()).catch(err => Log.error(err));
1078
+ ```
1079
+
1080
+ ```javascript
1081
+ /** Page.setBackButton + переключение роутов */
1082
+ const page = new MainPage();
1083
+ page.setBackButton({ title: 'Назад', icon: Icons.NAVIGATION.ARROW_BACK, reverse: true, page: new SecondPage() });
1084
+ ```
1085
+
1086
+ ```javascript
1087
+ /** Select, ComboBox, MultiComboBox, RadioGroup, Form */
1088
+ const select = new Select({ title: 'Select' });
1089
+ select.addOptions({ title: 'Пункт 1', value: '1' }, { title: 'Пункт 2', value: '2' });
1090
+
1091
+ const combo = new ComboBox({ title: 'ComboBox', width: '300px' });
1092
+ combo.addOptions({ title: 'Пункт', value: '1' });
1093
+
1094
+ const multi = new MultiComboBox({ title: 'MultiComboBox', width: '300px' });
1095
+ multi.addOptionsWithSections([{ title: 'Секция', options: [{ title: 'Пункт', value: '1' }] }]);
1096
+
1097
+ const group = new RadioGroup({ styles: { direction: Styles.DIRECTION.COLUMN } });
1098
+ group.addOptions([{ name: 'a', value: 'Вариант A' }, { name: 'b', value: 'Вариант B' }]);
1099
+
1100
+ try { group.getValue(); } catch (e) { Log.error(e); } // бросит, если ничего не выбрано
1101
+
1102
+ const form = new Form({
1103
+ method: Form.METHOD.POST,
1104
+ components: [
1105
+ new TextInput({ name: 'name', title: 'Имя', required: true }),
1106
+ new TextArea({ name: 'text', title: 'Сообщение', width: '300px', required: true }),
1107
+ Form.SubmitButton('Отправить')
1108
+ ],
1109
+ submitEvent: (e) => { e.preventDefault(); new Notification({ title: 'Form', text: 'Отправлено', showTime: 3000 }).show(); }
1110
+ });
1111
+ ```
1112
+
1113
+ ```javascript
1114
+ /** Table, TreeView, Tabs, ContextMenu */
1115
+ class Car { constructor(car, model, size) { this.car = car; this.model = model; this.size = size; } }
1116
+
1117
+ const table = new Table({ data: [new Car('Acura', 'NSX', 10), new Car('Ford', 'SHELBY', 34)], sorted: true });
1118
+ table.setFilterByProperty(Table.FILTER_TYPE.PARTIAL_MATCH, 'model', 'n');
1119
+
1120
+ const tree = new TreeView({
1121
+ width: Styles.SIZE.WEBKIT_FILL,
1122
+ components: [TreeView.ExpandButton({ title: 'Папка', subButtons: [], components: [new Paragraph('Внутри')] })]
1123
+ });
1124
+
1125
+ const first = new Tab('Вкладка 1');
1126
+ first.addContent(new TextInput({ title: 'Поле 1', width: '300px' }));
1127
+ const tabs = new Tabs({ default: 0, width: Styles.SIZE.WEBKIT_FILL, tabs: [first, new Tab('Вкладка 2')] });
1128
+
1129
+ const menu = new ContextMenu({
1130
+ items: [
1131
+ ContextMenu.Item({ title: 'Открыть', icon: Icons.NAVIGATION.APPS, clickEvent: () => {} }),
1132
+ ContextMenu.Separator(),
1133
+ ContextMenu.SubMenu({ title: 'Ещё', items: [ContextMenu.Item({ title: 'Свойство', clickEvent: () => {} })] })
1134
+ ]
1135
+ });
1136
+ menu.attach(table);
1137
+ ```
1138
+
1139
+ ```javascript
1140
+ /** Dialog + Popup */
1141
+ const dialog = new Dialog({ width: '420px', height: '260px', closeOutSideClick: true });
1142
+ dialog.addToHeader(new Paragraph('Dialog — модальное окно'));
1143
+ dialog.addToBody(new Paragraph('Содержимое задаётся через addToBody().'));
1144
+ dialog.addToFooter(new Button({ title: 'Понятно', clickEvent: () => dialog.close() }));
1145
+ dialog.open();
1146
+
1147
+ const popup = new Popup();
1148
+ popup.alert({ title: 'Внимание', message: 'Текст сообщения' });
1149
+ const ok = await popup.confirm({ title: 'Удалить?', message: 'Действие необратимо', okText: 'Удалить', cancelText: 'Отмена' });
1150
+ const data = await popup.prompt({ title: 'Ввод', message: 'Заполните поля', inputs: { text: { placeholder: 'Логин', errorMessage: 'Обязательное поле' }, password: { placeholder: 'Пароль' } } });
1151
+ ```
1152
+
1153
+ ```javascript
1154
+ /** TextEditor, SlideShow, графики, календарь */
1155
+ const editor = new TextEditor('100%', { title: 'TextEditor', controls: { BOLD: true, ITALIC: true, INSERT_LINK: true } });
1156
+
1157
+ const slideshow = new SlideShow({
1158
+ width: Styles.SIZE.WEBKIT_FILL, height: '260px',
1159
+ autoplay: { status: true, interval: 4000 },
1160
+ slides: [SlideShow.SLIDE({
1161
+ size: { width: Styles.SIZE.WEBKIT_FILL, height: Styles.SIZE.WEBKIT_FILL },
1162
+ style: { align: Styles.ALIGN.CENTER },
1163
+ components: [new H(3, 'Слайд 1'), new Paragraph('Текст')]
1164
+ })]
1165
+ });
1166
+
1167
+ const bar = new BarGraph({ data: { 'Пн': 4, 'Вт': 7 }, colors: ['#0a84ff', '#34c759'] });
1168
+ const pie = new PieGraph({ data: { 'Да': 30, 'Нет': 70 }, colors: ['#34c759', '#ff3b30'], legend: true, doughnutHoleSize: 0.5 });
1169
+
1170
+ const calendar = new Calendar(new Date().getFullYear(), new Date().getMonth() + 1);
1171
+ const weeks = calendar.getCalendar();
1172
+ ```
1173
+
1174
+ ## Модель безопасности renderer
1175
+
1176
+ Текущая конфигурация рассчитана на доверенный код приложения (всё, что грузится в окно,
1177
+ пишется вами), а не на загрузку сторонних страниц. Что включено сейчас:
1178
+
1179
+ - `process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = "true"` — глобально глушит предупреждения Electron о небезопасной конфигурации (`index.js`).
1180
+ - В `webPreferences` окна (`Main`): `nodeIntegration: true`, `contextIsolation: false`,
1181
+ `nodeIntegrationInSubFrames: true`, `webviewTag: true`, `enableRemoteModule: true`.
1182
+ - `preload` не является мостом: в него передаётся ваш renderer-энтрипоинт (`options.render`),
1183
+ то есть код страницы исполняется с полным доступом к Node.js.
1184
+ - `@electron/remote` инициализируется и включается для главного окна — renderer может
1185
+ напрямую обращаться к модулям main-процесса.
1186
+ - При `webSecurity: false` заголовки `Access-Control-Allow-Origin`, `Access-Control-Allow-Headers`
1187
+ и `X-Frame-Options` перезаписываются на `*` в `onBeforeSendHeaders` / `onHeadersReceived`.
1188
+ - Обработчик разрешений сессии автоматически одобряет только `media` (микрофон/камера),
1189
+ все остальные разрешения отклоняются.
1190
+
1191
+ Если приложение будет открывать внешний или недоверенный контент, безопасную схему нужно собрать так:
1192
+
1193
+ - `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true` (где возможно) — вместо `@electron/remote`.
1194
+ - Отдельный preload-скрипт с `contextBridge.exposeInMainWorld(...)` и узким списком методов;
1195
+ обмен с main-процессом через `ipcRenderer.invoke` / `ipcMain.handle` вместо `remote`.
1196
+ - `webviewTag` — только если он реально используется; иначе `false`.
1197
+ - `webSecurity` держать `true`, а внешние запросы проксировать через main-процесс,
1198
+ вместо перезаписи CORS-заголовков.
1199
+ - Ограничивать `loadURL`/`BrowserWindow`-навигацию через `will-navigate` и `setWindowOpenHandler`.
1200
+
1201
+ ### В разработке
1202
+
1203
+ - Дочерние `BrowserWindow` — отдельные окна приложения пока не поддерживаются.