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
@@ -1,19 +1,19 @@
1
- class CodeBlock {
2
- #chui_code = document.createElement(`code`);
3
- constructor(text = String(), options = { textAlign: String(), wordBreak: String(), width: String() }) {
4
- require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_code');
5
- this.#chui_code.innerText = text;
6
- this.#chui_code.classList.add("chui_code");
7
- if (options.textAlign !== undefined) this.#chui_code.style.textAlign = options.textAlign;
8
- if (options.wordBreak !== undefined) this.#chui_code.style.wordBreak = options.wordBreak;
9
- if (options.width !== undefined) this.#chui_code.style.width = options.width;
10
- }
11
- setText(text = String()) {
12
- this.#chui_code.innerText = text;
13
- }
14
- set() {
15
- return this.#chui_code;
16
- }
17
- }
18
-
1
+ class CodeBlock {
2
+ #chui_code = document.createElement(`code`);
3
+ constructor(text = String(), options = { textAlign: String(), wordBreak: String(), width: String() }) {
4
+ require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_code');
5
+ this.#chui_code.innerText = text;
6
+ this.#chui_code.classList.add("chui_code");
7
+ if (options.textAlign !== undefined) this.#chui_code.style.textAlign = options.textAlign;
8
+ if (options.wordBreak !== undefined) this.#chui_code.style.wordBreak = options.wordBreak;
9
+ if (options.width !== undefined) this.#chui_code.style.width = options.width;
10
+ }
11
+ setText(text = String()) {
12
+ this.#chui_code.innerText = text;
13
+ }
14
+ set() {
15
+ return this.#chui_code;
16
+ }
17
+ }
18
+
19
19
  exports.CodeBlock = CodeBlock
@@ -1,10 +1,10 @@
1
- .chui_code {
2
- height: max-content;
3
- width: max-content;
4
- margin: var(--margin);
5
- font-size: var(--font_default_size);
6
- font-weight: 400;
7
- color: var(--text_color);
8
- white-space: pre-wrap;
9
- font-family: monospace;
1
+ .chui_code {
2
+ height: max-content;
3
+ width: max-content;
4
+ margin: var(--margin);
5
+ font-size: var(--font_default_size);
6
+ font-weight: 400;
7
+ color: var(--text_color);
8
+ white-space: pre-wrap;
9
+ font-family: monospace;
10
10
  }
@@ -1,28 +1,28 @@
1
- class Console {
2
- #chui_console = document.createElement(`chui_console`);
3
- constructor(options = { width: String(), height: String() }) {
4
- require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_console');
5
-
6
- if (options.width !== undefined) {
7
- this.#chui_console.style.width = options.width
8
- } else {
9
- this.#chui_console.style.width = "max-content"
10
- }
11
-
12
- if (options.height !== undefined) {
13
- this.#chui_console.style.height = options.height
14
- } else {
15
- this.#chui_console.style.height = "max-content"
16
- }
17
- }
18
- addText(text = String()) {
19
- const chui_console_text = document.createElement(`chui_console_text`);
20
- chui_console_text.innerText = text;
21
- this.#chui_console.appendChild(chui_console_text)
22
- }
23
- set() {
24
- return this.#chui_console;
25
- }
26
- }
27
-
1
+ class Console {
2
+ #chui_console = document.createElement(`chui_console`);
3
+ constructor(options = { width: String(), height: String() }) {
4
+ require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_console');
5
+
6
+ if (options.width !== undefined) {
7
+ this.#chui_console.style.width = options.width
8
+ } else {
9
+ this.#chui_console.style.width = "max-content"
10
+ }
11
+
12
+ if (options.height !== undefined) {
13
+ this.#chui_console.style.height = options.height
14
+ } else {
15
+ this.#chui_console.style.height = "max-content"
16
+ }
17
+ }
18
+ addText(text = String()) {
19
+ const chui_console_text = document.createElement(`chui_console_text`);
20
+ chui_console_text.innerText = text;
21
+ this.#chui_console.appendChild(chui_console_text)
22
+ }
23
+ set() {
24
+ return this.#chui_console;
25
+ }
26
+ }
27
+
28
28
  exports.Console = Console
@@ -1,32 +1,32 @@
1
- chui_console {
2
- height: max-content;
3
- width: max-content;
4
- margin: var(--margin);
5
- font-size: var(--font_default_size);
6
- font-weight: 500;
7
- color: var(--text_color);
8
- white-space: break-spaces;
9
- word-wrap: break-word;
10
- font-family: monospace;
11
- user-select: text;
12
- border: 1px solid var(--border_color);
13
- padding: var(--test_padding);
14
- border-radius: var(--border_radius);
15
-
16
- display: flex;
17
- flex-direction: column;
18
- align-items: start;
19
- justify-content: start;
20
- overflow: auto;
21
- }
22
-
23
- chui_console_text {
24
- display: block;
25
- height: -webkit-fill-available;
26
- width: -webkit-fill-available;
27
- white-space: break-spaces;
28
- word-wrap: break-word;
29
- padding: var(--test_padding);
30
- text-align: start;
31
- user-select: text;
1
+ chui_console {
2
+ height: max-content;
3
+ width: max-content;
4
+ margin: var(--margin);
5
+ font-size: var(--font_default_size);
6
+ font-weight: 500;
7
+ color: var(--text_color);
8
+ white-space: break-spaces;
9
+ word-wrap: break-word;
10
+ font-family: monospace;
11
+ user-select: text;
12
+ border: 1px solid var(--border_color);
13
+ padding: var(--test_padding);
14
+ border-radius: var(--border_radius);
15
+
16
+ display: flex;
17
+ flex-direction: column;
18
+ align-items: start;
19
+ justify-content: start;
20
+ overflow: auto;
21
+ }
22
+
23
+ chui_console_text {
24
+ display: block;
25
+ height: -webkit-fill-available;
26
+ width: -webkit-fill-available;
27
+ white-space: break-spaces;
28
+ word-wrap: break-word;
29
+ padding: var(--test_padding);
30
+ text-align: start;
31
+ user-select: text;
32
32
  }
@@ -1,52 +1,52 @@
1
- const {Animation} = require("../../modules/chui_animations/animations");
2
-
3
- class ContentBlock {
4
- #body = document.createElement(`contentblock`);
5
- #disableMarginChild = false;
6
- constructor(options = {
7
- display: String(),
8
- direction: String(),
9
- wrap: String(),
10
- align: String(),
11
- justify: String(),
12
- disableMarginChild: Boolean(),
13
- }) {
14
- require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_ContentBlock');
15
- if (options.display !== undefined) this.#body.style.display = options.display;
16
- if (options.direction !== undefined) this.#body.style.flexDirection = options.direction;
17
- if (options.wrap !== undefined) this.#body.style.flexWrap = options.wrap;
18
- if (options.align !== undefined) this.#body.style.alignItems = options.align;
19
- if (options.justify !== undefined) this.#body.style.justifyContent = options.justify;
20
- this.#disableMarginChild = options.disableMarginChild;
21
- }
22
- remove(...components) {
23
- for (let component of components) new Animation(component.set()).fadeOutAndRemove();
24
- }
25
- // FUNCTIONS
26
- disableMarginChild() {
27
- for (let child of this.#body.children) child.style.margin = '0px';
28
- }
29
- add(...components) {
30
- for (let component of components) {
31
- if (this.#disableMarginChild) {
32
- let child = component.set();
33
- child.style.margin = "0px";
34
- new Animation(child).fadeIn()
35
- this.#body.appendChild(child);
36
- } else {
37
- new Animation(component.set()).fadeIn()
38
- this.#body.appendChild(component.set());
39
- }
40
- }
41
- }
42
- clear() { this.#body.innerHTML = ''; }
43
- // SETS
44
- setAutoOverflow(boolean = Boolean(false)) { if (boolean) this.#body.style.overflow = 'auto'; }
45
- setContentEditable(boolean= Boolean(false)) { this.#body.setAttribute('contenteditable', boolean); }
46
- setWidth(width = String()) { this.#body.style.width = width; }
47
- setHeight(height = String()) { this.#body.style.height = height; }
48
- setPadding(value = String()) { this.#body.style.padding = value; }
49
- set() { return this.#body; }
50
- }
51
-
1
+ const {Animation} = require("../../modules/chui_animations/animations");
2
+
3
+ class ContentBlock {
4
+ #body = document.createElement(`contentblock`);
5
+ #disableMarginChild = false;
6
+ constructor(options = {
7
+ display: String(),
8
+ direction: String(),
9
+ wrap: String(),
10
+ align: String(),
11
+ justify: String(),
12
+ disableMarginChild: Boolean(),
13
+ }) {
14
+ require('../../modules/chui_functions').setStyles(__dirname + "/styles.css", 'chUiJS_ContentBlock');
15
+ if (options.display !== undefined) this.#body.style.display = options.display;
16
+ if (options.direction !== undefined) this.#body.style.flexDirection = options.direction;
17
+ if (options.wrap !== undefined) this.#body.style.flexWrap = options.wrap;
18
+ if (options.align !== undefined) this.#body.style.alignItems = options.align;
19
+ if (options.justify !== undefined) this.#body.style.justifyContent = options.justify;
20
+ this.#disableMarginChild = options.disableMarginChild;
21
+ }
22
+ remove(...components) {
23
+ for (let component of components) new Animation(component.set()).fadeOutAndRemove();
24
+ }
25
+ // FUNCTIONS
26
+ disableMarginChild() {
27
+ for (let child of this.#body.children) child.style.margin = '0px';
28
+ }
29
+ add(...components) {
30
+ for (let component of components) {
31
+ if (this.#disableMarginChild) {
32
+ let child = component.set();
33
+ child.style.margin = "0px";
34
+ new Animation(child).fadeIn()
35
+ this.#body.appendChild(child);
36
+ } else {
37
+ new Animation(component.set()).fadeIn()
38
+ this.#body.appendChild(component.set());
39
+ }
40
+ }
41
+ }
42
+ clear() { this.#body.innerHTML = ''; }
43
+ // SETS
44
+ setAutoOverflow(boolean = Boolean(false)) { if (boolean) this.#body.style.overflow = 'auto'; }
45
+ setContentEditable(boolean= Boolean(false)) { this.#body.setAttribute('contenteditable', boolean); }
46
+ setWidth(width = String()) { this.#body.style.width = width; }
47
+ setHeight(height = String()) { this.#body.style.height = height; }
48
+ setPadding(value = String()) { this.#body.style.padding = value; }
49
+ set() { return this.#body; }
50
+ }
51
+
52
52
  exports.ContentBlock = ContentBlock
@@ -1,3 +1,3 @@
1
- contentblock {
2
- display: flex
1
+ contentblock {
2
+ display: flex
3
3
  }
@@ -0,0 +1,230 @@
1
+ const { setStyles } = require("../../modules/chui_functions");
2
+ const { Icon, Icons } = require("../chui_icons/icons");
3
+
4
+ class ContextMenu {
5
+ #menu = document.createElement("chui_context_menu");
6
+ #targets = [];
7
+ #visible = false;
8
+ #hideHandler = undefined;
9
+
10
+ constructor(options = { items: [] }) {
11
+ setStyles(__dirname + "/styles.css", "chUiJS_ContextMenu");
12
+ this.#renderItems(this.#menu, options.items);
13
+ this.#menu.addEventListener("contextmenu", (event) => event.preventDefault());
14
+ document.body.appendChild(this.#menu);
15
+ }
16
+
17
+ // === СТАТИЧЕСКИЕ ОПИСАНИЯ ПУНКТОВ ===
18
+ static Item(
19
+ options = { title: String(), icon: String(), shortcut: String(), disabled: Boolean(), clickEvent: () => {} }
20
+ ) {
21
+ return { type: "item", ...options };
22
+ }
23
+ static Separator() {
24
+ return { type: "separator" };
25
+ }
26
+ static SubMenu(options = { title: String(), icon: String(), items: [] }) {
27
+ return { type: "submenu", ...options };
28
+ }
29
+
30
+ // === ПОСТРОЕНИЕ ===
31
+ #renderItems(container, items) {
32
+ if (items === undefined) return;
33
+ for (let item of items) {
34
+ if (item.type === "separator") {
35
+ container.appendChild(document.createElement("context_menu_separator"));
36
+ continue;
37
+ }
38
+ let row = document.createElement("context_menu_item");
39
+ if (item.icon) {
40
+ let iconWrap = document.createElement("context_menu_item_icon");
41
+ iconWrap.innerHTML = new Icon(item.icon).getHTML();
42
+ row.appendChild(iconWrap);
43
+ }
44
+ let title = document.createElement("context_menu_item_title");
45
+ if (item.title !== undefined) title.innerText = item.title;
46
+ row.appendChild(title);
47
+
48
+ if (item.type === "submenu") {
49
+ if (item.shortcut) {
50
+ let shortcut = document.createElement("context_menu_item_shortcut");
51
+ shortcut.innerText = item.shortcut;
52
+ row.appendChild(shortcut);
53
+ }
54
+ let arrow = document.createElement("context_menu_item_arrow");
55
+ arrow.innerHTML = new Icon(Icons.NAVIGATION.CHEVRON_RIGHT).getHTML();
56
+ if (!item.shortcut) arrow.style.marginLeft = "auto";
57
+ row.appendChild(arrow);
58
+ let submenu = document.createElement("context_menu_submenu");
59
+ this.#renderItems(submenu, item.items);
60
+ row.appendChild(submenu);
61
+ row.addEventListener("mouseenter", () => this.#openSubMenu(row));
62
+ row.addEventListener("mouseleave", () => this.#closeSubMenu(row));
63
+ } else {
64
+ if (item.disabled === true) {
65
+ row.classList.add("context_menu_item_disabled");
66
+ } else {
67
+ row.addEventListener("click", () => {
68
+ if (typeof item.clickEvent === "function") item.clickEvent();
69
+ this.hide();
70
+ });
71
+ }
72
+ if (item.shortcut) {
73
+ let shortcut = document.createElement("context_menu_item_shortcut");
74
+ shortcut.innerText = item.shortcut;
75
+ row.appendChild(shortcut);
76
+ }
77
+ }
78
+ container.appendChild(row);
79
+ }
80
+ }
81
+
82
+ // === ПОДМЕНЮ ===
83
+ #openSubMenu(row) {
84
+ let submenu = row.querySelector("context_menu_submenu");
85
+ if (submenu === null) return;
86
+ // Закрыть соседние подменю того же уровня
87
+ let parent = row.parentElement;
88
+ for (let child of parent.children) {
89
+ if (child === row) continue;
90
+ let sibling = child.querySelector("context_menu_submenu");
91
+ if (sibling !== null) sibling.style.display = "none";
92
+ }
93
+ submenu.style.display = "flex";
94
+ submenu.style.left = "100%";
95
+ submenu.style.top = "0px";
96
+ let rowRect = row.getBoundingClientRect();
97
+ let subRect = submenu.getBoundingClientRect();
98
+ // Флип влево у правого края окна
99
+ if (subRect.right > window.innerWidth - 8) {
100
+ submenu.style.left = "-100%";
101
+ subRect = submenu.getBoundingClientRect();
102
+ }
103
+ // Флип вверх у нижнего края окна
104
+ let overflow = rowRect.top + subRect.height - (window.innerHeight - 8);
105
+ if (overflow > 0) {
106
+ let shift = Math.min(overflow, rowRect.top - 8);
107
+ submenu.style.top = -shift + "px";
108
+ }
109
+ }
110
+ #closeSubMenu(row) {
111
+ let submenu = row.querySelector("context_menu_submenu");
112
+ if (submenu !== null) submenu.style.display = "none";
113
+ }
114
+
115
+ // === ПОКАЗ / СКРЫТИЕ ===
116
+ attach(target) {
117
+ let element = typeof target.set === "function" ? target.set() : target;
118
+ let listener = (event) => {
119
+ event.preventDefault();
120
+ this.show(event.clientX, event.clientY);
121
+ };
122
+ element.addEventListener("contextmenu", listener);
123
+ this.#targets.push({ element: element, listener: listener });
124
+ return this;
125
+ }
126
+ detach(target) {
127
+ let element = typeof target.set === "function" ? target.set() : target;
128
+ for (let i = 0; i < this.#targets.length; i++) {
129
+ if (this.#targets[i].element === element) {
130
+ element.removeEventListener("contextmenu", this.#targets[i].listener);
131
+ this.#targets.splice(i, 1);
132
+ return;
133
+ }
134
+ }
135
+ }
136
+ show(x = 0, y = 0) {
137
+ this.#cancelPendingHide();
138
+ // Сбросить геометрию подменю от прошлого показа
139
+ for (let submenu of this.#menu.querySelectorAll("context_menu_submenu")) {
140
+ submenu.style.display = "none";
141
+ submenu.style.left = "100%";
142
+ submenu.style.top = "0px";
143
+ }
144
+ if (!this.#visible) {
145
+ window.addEventListener("click", this.#onWindowClick, true);
146
+ window.addEventListener("contextmenu", this.#onWindowContextMenu, true);
147
+ window.addEventListener("keydown", this.#onWindowKeyDown);
148
+ window.addEventListener("resize", this.#onWindowResize);
149
+ window.addEventListener("blur", this.#onWindowBlur);
150
+ this.#visible = true;
151
+ }
152
+ // Убрать/зафиксировать переходы, чтобы перенос координат не анимировался
153
+ this.#menu.style.transition = "none";
154
+ // Позиционируем и измеряем скрытым, чтобы ни один кадр не отрисовался в (0, 0)
155
+ this.#menu.style.visibility = "hidden";
156
+ this.#menu.style.display = "flex";
157
+ this.#menu.style.left = "0px";
158
+ this.#menu.style.top = "0px";
159
+ // Позиция с учётом краёв окна
160
+ let left = Math.max(8, Math.min(x, window.innerWidth - this.#menu.offsetWidth - 8));
161
+ let top = Math.max(8, Math.min(y, window.innerHeight - this.#menu.offsetHeight - 8));
162
+ this.#menu.style.left = left + "px";
163
+ this.#menu.style.top = top + "px";
164
+ // Зафиксировать финальную геометрию до показа
165
+ void this.#menu.offsetWidth;
166
+ // Показ и перезапуск анимации уже в финальной позиции
167
+ this.#menu.style.visibility = "visible";
168
+ this.#menu.style.animationName = "none";
169
+ void this.#menu.offsetWidth;
170
+ this.#menu.style.animationName = "context-scale-in";
171
+ }
172
+ hide() {
173
+ if (!this.#visible) return;
174
+ this.#visible = false;
175
+ window.removeEventListener("click", this.#onWindowClick, true);
176
+ window.removeEventListener("contextmenu", this.#onWindowContextMenu, true);
177
+ window.removeEventListener("keydown", this.#onWindowKeyDown);
178
+ window.removeEventListener("resize", this.#onWindowResize);
179
+ window.removeEventListener("blur", this.#onWindowBlur);
180
+ // Анимация скрытия
181
+ this.#menu.style.animationName = "none";
182
+ void this.#menu.offsetWidth;
183
+ this.#menu.style.animationName = "context-scale-out";
184
+ this.#hideHandler = (event) => {
185
+ if (event.target !== this.#menu) return;
186
+ this.#menu.removeEventListener("animationend", this.#hideHandler);
187
+ this.#menu.style.display = "none";
188
+ this.#menu.style.removeProperty("animation-name");
189
+ this.#hideHandler = undefined;
190
+ };
191
+ this.#menu.addEventListener("animationend", this.#hideHandler);
192
+ }
193
+ #cancelPendingHide() {
194
+ if (this.#hideHandler !== undefined) {
195
+ this.#menu.removeEventListener("animationend", this.#hideHandler);
196
+ this.#menu.style.removeProperty("animation-name");
197
+ this.#hideHandler = undefined;
198
+ }
199
+ }
200
+ destroy() {
201
+ this.hide();
202
+ for (let target of this.#targets) {
203
+ target.element.removeEventListener("contextmenu", target.listener);
204
+ }
205
+ this.#targets = [];
206
+ this.#menu.remove();
207
+ }
208
+ set() {
209
+ return this.#menu;
210
+ }
211
+
212
+ // === СЛУШАТЕЛИ ОКНА ===
213
+ #onWindowClick = (event) => {
214
+ if (!this.#menu.contains(event.target)) this.hide();
215
+ };
216
+ #onWindowContextMenu = (event) => {
217
+ if (!this.#menu.contains(event.target)) this.hide();
218
+ };
219
+ #onWindowKeyDown = (event) => {
220
+ if (event.key === "Escape") this.hide();
221
+ };
222
+ #onWindowResize = () => {
223
+ this.hide();
224
+ };
225
+ #onWindowBlur = () => {
226
+ this.hide();
227
+ };
228
+ }
229
+
230
+ exports.ContextMenu = ContextMenu;
@@ -0,0 +1,136 @@
1
+ /* ── ContextMenu — контекстное меню ───────────────────────── */
2
+ @keyframes context-scale-in {
3
+ from {
4
+ opacity: 0;
5
+ transform: scale(0.92);
6
+ }
7
+ to {
8
+ opacity: 1;
9
+ transform: scale(1);
10
+ }
11
+ }
12
+ @keyframes context-scale-out {
13
+ from {
14
+ opacity: 1;
15
+ transform: scale(1);
16
+ }
17
+ to {
18
+ opacity: 0;
19
+ transform: scale(0.92);
20
+ }
21
+ }
22
+
23
+ /* Общая "стеклянная" поверхность меню и подменю (как у notification / menu_bar_dropdown) */
24
+ chui_context_menu,
25
+ context_menu_submenu {
26
+ display: none;
27
+ flex-direction: column;
28
+ align-items: stretch;
29
+ width: max-content;
30
+ min-width: 180px;
31
+ height: max-content;
32
+ padding: var(--margin);
33
+ background-color: var(--header_background);
34
+ color: var(--text_color);
35
+ border: 1px solid var(--border_color);
36
+ border-radius: var(--border_radius);
37
+ box-shadow: var(--box_shadow_main);
38
+ backdrop-filter: var(--global_blur);
39
+ -webkit-backdrop-filter: var(--global_blur);
40
+ font-size: var(--font_default_size);
41
+ }
42
+
43
+ chui_context_menu {
44
+ position: fixed;
45
+ z-index: 5000;
46
+ transform-origin: top left;
47
+ }
48
+
49
+ context_menu_submenu {
50
+ position: absolute;
51
+ z-index: 10;
52
+ top: 0;
53
+ left: 100%;
54
+ transition: none;
55
+ }
56
+
57
+ context_menu_item {
58
+ display: flex;
59
+ flex-direction: row;
60
+ align-items: center;
61
+ position: relative;
62
+ width: -webkit-fill-available;
63
+ white-space: nowrap;
64
+ cursor: pointer;
65
+ padding: var(--main_padding);
66
+ border-radius: var(--border_radius);
67
+ color: var(--text_color);
68
+ gap: 6px;
69
+ box-sizing: border-box;
70
+ }
71
+
72
+ context_menu_item:hover {
73
+ background: var(--blue_prime_background);
74
+ color: var(--text_color_hover);
75
+ }
76
+
77
+ context_menu_item_icon {
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ width: 18px;
82
+ flex: none;
83
+ }
84
+
85
+ context_menu_item_icon chui_icon {
86
+ font-size: 15px;
87
+ color: inherit;
88
+ }
89
+
90
+ context_menu_item_title {
91
+ display: block;
92
+ font-size: var(--font_default_size);
93
+ font-weight: 500;
94
+ color: inherit;
95
+ }
96
+
97
+ context_menu_item_arrow {
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ width: 14px;
102
+ flex: none;
103
+ }
104
+
105
+ context_menu_item_arrow chui_icon {
106
+ font-size: 14px;
107
+ color: inherit;
108
+ }
109
+
110
+ context_menu_item:hover chui_icon {
111
+ color: var(--text_color_hover);
112
+ }
113
+
114
+ context_menu_item_shortcut {
115
+ display: block;
116
+ margin-left: auto;
117
+ padding-left: 24px;
118
+ font-size: var(--font_labels_size);
119
+ color: var(--text_color_disabled);
120
+ }
121
+
122
+ context_menu_item:hover context_menu_item_shortcut {
123
+ color: var(--text_color_hover_disable);
124
+ }
125
+
126
+ context_menu_separator {
127
+ height: 1px;
128
+ margin: 4px 6px;
129
+ background: var(--border_color);
130
+ }
131
+
132
+ .context_menu_item_disabled {
133
+ cursor: default;
134
+ opacity: 0.5;
135
+ pointer-events: none;
136
+ }