chuijs 4.0.1 → 4.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/README.md +43 -632
- package/framework/appLayout/default/chui_app_layout.js +314 -273
- package/framework/appLayout/default/global_style.css +93 -53
- package/framework/components/chui_accordion/accordion.js +29 -26
- package/framework/components/chui_accordion/styles.css +6 -5
- package/framework/components/chui_action_sheet/action_sheet.js +59 -33
- package/framework/components/chui_action_sheet/styles.css +2 -1
- package/framework/components/chui_affix/affix.js +11 -6
- package/framework/components/chui_alert/alert.js +23 -14
- package/framework/components/chui_avatar/avatar.js +23 -22
- package/framework/components/chui_avatar_group/avatar_group.js +21 -19
- package/framework/components/chui_backtop/backtop.js +35 -19
- package/framework/components/chui_badge/badge.js +15 -13
- package/framework/components/chui_breadcrumb/breadcrumb.js +15 -13
- package/framework/components/chui_button/button.js +42 -22
- package/framework/components/chui_button/styles.css +2 -2
- package/framework/components/chui_calendar/calendar.js +12 -10
- package/framework/components/chui_card/card.js +43 -23
- package/framework/components/chui_chat/chat.js +72 -33
- package/framework/components/chui_clipboard_history/clipboard_history.js +30 -21
- package/framework/components/chui_clipboard_history/styles.css +4 -2
- package/framework/components/chui_code/code.js +2 -2
- package/framework/components/chui_code/styles.css +2 -2
- package/framework/components/chui_code_editor/code_editor.js +47 -31
- package/framework/components/chui_code_editor/styles.css +3 -2
- package/framework/components/chui_command_palette/command_palette.js +60 -37
- package/framework/components/chui_console/console.js +5 -5
- package/framework/components/chui_console/styles.css +1 -1
- package/framework/components/chui_content_block/content_block.js +33 -19
- package/framework/components/chui_content_block/styles.css +2 -2
- package/framework/components/chui_context_menu/context_menu.js +9 -4
- package/framework/components/chui_custom_element/custom_element.js +22 -15
- package/framework/components/chui_data_grid/data_grid.js +396 -234
- package/framework/components/chui_data_grid/styles.css +10 -8
- package/framework/components/chui_descriptions/descriptions.js +34 -17
- package/framework/components/chui_details/details.js +32 -26
- package/framework/components/chui_details/styles.css +5 -4
- package/framework/components/chui_diff_viewer/diff_viewer.js +91 -66
- package/framework/components/chui_diff_viewer/styles.css +1 -1
- package/framework/components/chui_divider/divider.js +13 -11
- package/framework/components/chui_drawer/drawer.js +27 -25
- package/framework/components/chui_drop_zone/drop_zone.js +31 -21
- package/framework/components/chui_empty_state/empty_state.js +20 -15
- package/framework/components/chui_fieldset/fieldset.js +37 -20
- package/framework/components/chui_fieldset/styles.css +1 -1
- package/framework/components/chui_file_explorer/file_explorer.js +157 -86
- package/framework/components/chui_file_explorer/styles.css +2 -1
- package/framework/components/chui_form/form.js +31 -19
- package/framework/components/chui_form/styles.css +1 -1
- package/framework/components/chui_gantt/gantt.js +64 -40
- package/framework/components/chui_graphs/graphs.js +301 -200
- package/framework/components/chui_graphs/styles.css +1 -1
- package/framework/components/chui_hero/hero.js +40 -22
- package/framework/components/chui_html_block/html_block.js +6 -6
- package/framework/components/chui_html_block/styles.css +2 -2
- package/framework/components/chui_hx/hx.js +3 -3
- package/framework/components/chui_hx/styles.css +11 -4
- package/framework/components/chui_icons/icons.js +2109 -2109
- package/framework/components/chui_icons/styles.css +2 -2
- package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
- package/framework/components/chui_inputs/chui_check_box/styles.css +18 -18
- package/framework/components/chui_inputs/chui_chips/chips.js +50 -40
- package/framework/components/chui_inputs/chui_chips/styles.css +4 -2
- package/framework/components/chui_inputs/chui_color_picker/color_picker.js +36 -24
- package/framework/components/chui_inputs/chui_combo_box/combo_box.js +100 -82
- package/framework/components/chui_inputs/chui_combo_box/styles.css +14 -12
- package/framework/components/chui_inputs/chui_date/date.js +102 -66
- package/framework/components/chui_inputs/chui_date/styles.css +12 -11
- package/framework/components/chui_inputs/chui_date_range/date_range.js +40 -29
- package/framework/components/chui_inputs/chui_datetime/datetime.js +35 -27
- package/framework/components/chui_inputs/chui_email/email.js +43 -31
- package/framework/components/chui_inputs/chui_email/styles.css +5 -5
- package/framework/components/chui_inputs/chui_file/file.js +68 -41
- package/framework/components/chui_inputs/chui_file/styles.css +13 -12
- package/framework/components/chui_inputs/chui_hotkey/hotkey.js +61 -36
- package/framework/components/chui_inputs/chui_masked/masked.js +39 -28
- package/framework/components/chui_inputs/chui_mention/mention.js +77 -30
- package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +173 -153
- package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +9 -7
- package/framework/components/chui_inputs/chui_number/number.js +58 -41
- package/framework/components/chui_inputs/chui_number/styles.css +7 -7
- package/framework/components/chui_inputs/chui_otp/otp.js +38 -29
- package/framework/components/chui_inputs/chui_password/password.js +58 -36
- package/framework/components/chui_inputs/chui_password/styles.css +8 -8
- package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
- package/framework/components/chui_inputs/chui_radio_button/styles.css +20 -20
- package/framework/components/chui_inputs/chui_rating/rating.js +26 -22
- package/framework/components/chui_inputs/chui_select_box/select_box.js +102 -78
- package/framework/components/chui_inputs/chui_select_box/styles.css +11 -11
- package/framework/components/chui_inputs/chui_slider/slider.js +29 -26
- package/framework/components/chui_inputs/chui_slider/styles.css +2 -1
- package/framework/components/chui_inputs/chui_text/styles.css +8 -8
- package/framework/components/chui_inputs/chui_text/text.js +65 -44
- package/framework/components/chui_inputs/chui_text_area/styles.css +6 -6
- package/framework/components/chui_inputs/chui_text_area/text_area.js +51 -28
- package/framework/components/chui_inputs/chui_time/styles.css +5 -3
- package/framework/components/chui_inputs/chui_time/time.js +38 -27
- package/framework/components/chui_inputs/chui_toggle/styles.css +3 -3
- package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
- package/framework/components/chui_json_editor/json_editor.js +53 -43
- package/framework/components/chui_json_viewer/json_viewer.js +52 -33
- package/framework/components/chui_json_viewer/styles.css +3 -2
- package/framework/components/chui_kanban/kanban.js +52 -34
- package/framework/components/chui_kbd/kbd.js +12 -10
- package/framework/components/chui_label/label.js +14 -12
- package/framework/components/chui_label/styles.css +2 -2
- package/framework/components/chui_link/link.js +27 -20
- package/framework/components/chui_list/list.js +31 -27
- package/framework/components/chui_list/styles.css +2 -1
- package/framework/components/chui_markdown_editor/markdown_editor.js +60 -44
- package/framework/components/chui_media/audio.js +326 -227
- package/framework/components/chui_media/audio_styles.css +40 -21
- package/framework/components/chui_media/image.js +70 -38
- package/framework/components/chui_media/image_compare.js +52 -21
- package/framework/components/chui_media/image_styles.css +3 -3
- package/framework/components/chui_media/video.js +155 -62
- package/framework/components/chui_media/video_styles.css +33 -16
- package/framework/components/chui_menu_bar/menu_bar.js +26 -24
- package/framework/components/chui_menu_bar/styles.css +4 -2
- package/framework/components/chui_modal/modal.js +44 -43
- package/framework/components/chui_modal/styles.css +3 -2
- package/framework/components/chui_notification/notification.js +50 -34
- package/framework/components/chui_notification/notification_download.js +31 -25
- package/framework/components/chui_notification/notification_download_progress.css +3 -2
- package/framework/components/chui_notification/notification_download_progress.js +47 -41
- package/framework/components/chui_notification/notification_update.js +23 -16
- package/framework/components/chui_notification/status_plate.js +2 -2
- package/framework/components/chui_notification/styles_download.css +1 -1
- package/framework/components/chui_notification/styles_update.css +1 -1
- package/framework/components/chui_org_chart/org_chart.js +74 -35
- package/framework/components/chui_pagination/pagination.js +62 -27
- package/framework/components/chui_paragraph/paragraph.js +2 -2
- package/framework/components/chui_paragraph/styles.css +1 -1
- package/framework/components/chui_popover/popover.js +49 -28
- package/framework/components/chui_popups/popups.js +137 -108
- package/framework/components/chui_popups/styles.css +2 -2
- package/framework/components/chui_print_button/print_button.js +33 -23
- package/framework/components/chui_progress_bar/progress_bar.js +23 -15
- package/framework/components/chui_progress_bar/styles.css +2 -2
- package/framework/components/chui_progress_ring/progress_ring.js +17 -15
- package/framework/components/chui_qrcode/qrcode.js +75 -31
- package/framework/components/chui_query_builder/query_builder.js +216 -123
- package/framework/components/chui_radio_group/radio_group.js +36 -26
- package/framework/components/chui_radio_group/styles.css +1 -1
- package/framework/components/chui_result/result.js +26 -16
- package/framework/components/chui_scheduler/scheduler.js +57 -35
- package/framework/components/chui_scheduler/styles.css +6 -2
- package/framework/components/chui_segmented_control/segmented_control.js +27 -21
- package/framework/components/chui_settings/settings.js +122 -48
- package/framework/components/chui_shortcut_help/shortcut_help.js +55 -32
- package/framework/components/chui_sidebar_nav/sidebar_nav.js +33 -23
- package/framework/components/chui_skeleton/skeleton.js +29 -24
- package/framework/components/chui_slideshow/slideshow.js +60 -41
- package/framework/components/chui_slideshow/styles.css +18 -12
- package/framework/components/chui_spinner/spinner.js +8 -8
- package/framework/components/chui_spinner/styles.css +0 -1
- package/framework/components/chui_split_button/split_button.js +23 -18
- package/framework/components/chui_split_button/styles.css +1 -1
- package/framework/components/chui_splitter/splitter.js +35 -28
- package/framework/components/chui_statistic/statistic.js +65 -38
- package/framework/components/chui_status_bar/status_bar.js +11 -9
- package/framework/components/chui_stepper/stepper.js +27 -22
- package/framework/components/chui_sticky_bar/sticky_bar.js +9 -4
- package/framework/components/chui_sticky_bar/styles.css +3 -1
- package/framework/components/chui_tabs/chui_tabs.js +28 -22
- package/framework/components/chui_tabs/styles.css +3 -3
- package/framework/components/chui_text_editor/styles.css +20 -19
- package/framework/components/chui_text_editor/text_editor.js +85 -61
- package/framework/components/chui_text_editor/text_editor_panel.js +346 -284
- package/framework/components/chui_theme_toggle/theme_toggle.js +27 -22
- package/framework/components/chui_timeline/timeline.js +14 -12
- package/framework/components/chui_toast_queue/toast_queue.js +52 -46
- package/framework/components/chui_toolbar/toolbar.js +20 -13
- package/framework/components/chui_tooltip/tooltip.js +37 -24
- package/framework/components/chui_tour/styles.css +8 -3
- package/framework/components/chui_tour/tour.js +61 -27
- package/framework/components/chui_transfer/styles.css +2 -1
- package/framework/components/chui_transfer/transfer.js +75 -42
- package/framework/components/chui_tree_table/tree_table.js +43 -29
- package/framework/components/chui_tree_view/styles.css +4 -2
- package/framework/components/chui_tree_view/tree_view.js +142 -99
- package/framework/components/chui_watermark/watermark.js +24 -16
- package/framework/components/chui_webview/styles.css +2 -2
- package/framework/components/chui_webview/webview.js +62 -50
- package/framework/components/telegram_bot/chui_telegram_bot.js +71 -64
- package/framework/modules/chui_a11y/chui_a11y.js +77 -46
- package/framework/modules/chui_animations/animations.js +42 -42
- package/framework/modules/chui_animations/styles.css +1 -1
- package/framework/modules/chui_bridge/preload.js +56 -26
- package/framework/modules/chui_functions.js +52 -50
- package/framework/modules/chui_highlight/chui_highlight.js +25 -22
- package/framework/modules/chui_i18n/chui_i18n.js +9 -7
- package/framework/modules/chui_i18n/locales/a11y.js +5 -5
- package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
- package/framework/modules/chui_i18n/locales/calendar.js +3 -3
- package/framework/modules/chui_i18n/locales/common.js +3 -3
- package/framework/modules/chui_i18n/locales/data.js +36 -26
- package/framework/modules/chui_i18n/locales/editors.js +40 -13
- package/framework/modules/chui_i18n/locales/feedback.js +3 -3
- package/framework/modules/chui_i18n/locales/forms.js +8 -8
- package/framework/modules/chui_i18n/locales/graphics.js +8 -8
- package/framework/modules/chui_i18n/locales/media.js +4 -4
- package/framework/modules/chui_i18n/locales/navigation.js +8 -8
- package/framework/modules/chui_i18n/locales/validate.js +3 -3
- package/framework/modules/chui_json/chui_json.js +9 -9
- package/framework/modules/chui_layout/chui_layout.js +12 -10
- package/framework/modules/chui_logger/chui_logger.js +29 -25
- package/framework/modules/chui_page/page.js +33 -27
- package/framework/modules/chui_page/styles.css +3 -26
- package/framework/modules/chui_qr/chui_qr.js +158 -67
- package/framework/modules/chui_scrollbar/chui_scrollbar.js +35 -24
- package/framework/modules/chui_scrollbar/styles.css +3 -1
- package/framework/modules/chui_services/chui_channels.js +76 -11
- package/framework/modules/chui_services/chui_services.js +39 -31
- package/framework/modules/chui_services/desktop.js +149 -30
- package/framework/modules/chui_sortable/chui_sortable.js +17 -12
- package/framework/modules/chui_validate/chui_validate.js +7 -4
- package/framework/tray_bar/chui_menu_items.js +136 -46
- package/index.d.ts +4814 -0
- package/index.js +449 -334
- package/package.json +79 -68
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const {Icon, Icons} = require("../chui_icons/icons");
|
|
2
|
-
const {makeKeyboardButton} = require("../../modules/chui_a11y/chui_a11y");
|
|
3
|
-
const {I18n} = require("../../modules/chui_i18n/chui_i18n");
|
|
1
|
+
const { Icon, Icons } = require("../chui_icons/icons");
|
|
2
|
+
const { makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
|
|
3
|
+
const { I18n } = require("../../modules/chui_i18n/chui_i18n");
|
|
4
4
|
|
|
5
5
|
/** Идентификаторы панелей дерева для aria-controls */
|
|
6
6
|
let tree_panel_counter = 0;
|
|
@@ -11,7 +11,7 @@ const DRAGGING_CLASS = "tree_view_button_dragging";
|
|
|
11
11
|
const DROP_CLASSES = {
|
|
12
12
|
before: "tree_view_button_drop_before",
|
|
13
13
|
after: "tree_view_button_drop_after",
|
|
14
|
-
inside: "tree_view_button_drop_inside"
|
|
14
|
+
inside: "tree_view_button_drop_inside",
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -58,12 +58,12 @@ class TreeView {
|
|
|
58
58
|
static SELECTION = {
|
|
59
59
|
NONE: "none",
|
|
60
60
|
SINGLE: "single",
|
|
61
|
-
MULTIPLE: "multiple"
|
|
61
|
+
MULTIPLE: "multiple",
|
|
62
62
|
};
|
|
63
63
|
static DROP = {
|
|
64
64
|
BEFORE: "before",
|
|
65
65
|
AFTER: "after",
|
|
66
|
-
INSIDE: "inside"
|
|
66
|
+
INSIDE: "inside",
|
|
67
67
|
};
|
|
68
68
|
// Узел → описание из фабрики: коллбэки и getSelected() отдают то же, что вернул TreeView.Button
|
|
69
69
|
static #registry = new WeakMap();
|
|
@@ -112,36 +112,42 @@ class TreeView {
|
|
|
112
112
|
#raf = null;
|
|
113
113
|
#destroyed = false;
|
|
114
114
|
|
|
115
|
-
constructor(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
115
|
+
constructor(
|
|
116
|
+
options = {
|
|
117
|
+
width: String(),
|
|
118
|
+
components: [],
|
|
119
|
+
selection: String(),
|
|
120
|
+
draggable: Boolean(),
|
|
121
|
+
moveOnDrop: Boolean(),
|
|
122
|
+
label: String(),
|
|
123
|
+
onSelect: undefined,
|
|
124
|
+
onDrop: undefined,
|
|
125
|
+
deferRender: Boolean(),
|
|
126
|
+
virtual: undefined,
|
|
127
|
+
variableHeight: Boolean(),
|
|
128
|
+
estimatedRowHeight: undefined,
|
|
129
|
+
height: String(),
|
|
130
|
+
}
|
|
131
|
+
) {
|
|
132
|
+
require("../../modules/chui_functions").setStyles(__dirname + "/styles.css", "chUiJS_TreeView");
|
|
131
133
|
let main_test = document.createElement(`chui_tree_view_main_test`);
|
|
132
134
|
main_test.setAttribute("role", "tree");
|
|
133
135
|
this.#main_test = main_test;
|
|
134
136
|
// Виртуализация включается явно: `virtual: true` или `{rowHeight, buffer}`
|
|
135
|
-
this.#virtual =
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
this.#virtual =
|
|
138
|
+
options.virtual === undefined || options.virtual === false
|
|
139
|
+
? undefined
|
|
140
|
+
: options.virtual === true
|
|
141
|
+
? {}
|
|
142
|
+
: { ...options.virtual };
|
|
138
143
|
// Переменная высота: функция `rowHeight(record, index)` — высоты из данных,
|
|
139
144
|
// `variableHeight: true` — высоты измеряются по отрисованным строкам
|
|
140
145
|
if (this.#virtual !== undefined) {
|
|
141
146
|
if (options.variableHeight === true) this.#virtual.variableHeight = true;
|
|
142
147
|
this.#variable_height = typeof this.#virtual.rowHeight === "function";
|
|
143
148
|
this.#measure_dom = this.#virtual.variableHeight === true;
|
|
144
|
-
if (this.#measure_dom && this.#virtual.estimatedRowHeight === undefined)
|
|
149
|
+
if (this.#measure_dom && this.#virtual.estimatedRowHeight === undefined)
|
|
150
|
+
this.#virtual.estimatedRowHeight = 32;
|
|
145
151
|
}
|
|
146
152
|
this.#selection_mode = options.selection === undefined ? TreeView.SELECTION.NONE : options.selection;
|
|
147
153
|
this.#draggable = options.draggable === true;
|
|
@@ -152,7 +158,10 @@ class TreeView {
|
|
|
152
158
|
// Отложенная отрисовка строк: узлы в DOM, но далёкие от окна не раскладываются
|
|
153
159
|
if (options.deferRender === true) main_test.classList.add("tree_view_defer");
|
|
154
160
|
if (this.#selection_mode !== TreeView.SELECTION.NONE) {
|
|
155
|
-
main_test.setAttribute(
|
|
161
|
+
main_test.setAttribute(
|
|
162
|
+
"aria-multiselectable",
|
|
163
|
+
this.#selection_mode === TreeView.SELECTION.MULTIPLE ? "true" : "false"
|
|
164
|
+
);
|
|
156
165
|
main_test.addEventListener("click", this.#onClick);
|
|
157
166
|
// Space приходит и от makeKeyboardButton (кликом), поэтому перехватываем его
|
|
158
167
|
// с модификатором раньше — в фазе перехвата
|
|
@@ -170,23 +179,24 @@ class TreeView {
|
|
|
170
179
|
if (options.width !== undefined) {
|
|
171
180
|
this.#chui_tree_view.style.width = options.width;
|
|
172
181
|
} else {
|
|
173
|
-
this.#chui_tree_view.style.width =
|
|
182
|
+
this.#chui_tree_view.style.width = "max-content";
|
|
174
183
|
}
|
|
175
184
|
// Панели действий приходят опцией: частичный объект опций без них — дерево без панелей
|
|
176
185
|
const toolbar_buttons = Array.isArray(options.components) ? options.components : [];
|
|
177
|
-
toolbar_buttons.forEach(buttons => {
|
|
178
|
-
main_test.appendChild(buttons.button)
|
|
186
|
+
toolbar_buttons.forEach((buttons) => {
|
|
187
|
+
main_test.appendChild(buttons.button);
|
|
179
188
|
if (buttons.panel !== undefined) {
|
|
180
|
-
main_test.appendChild(buttons.panel)
|
|
189
|
+
main_test.appendChild(buttons.panel);
|
|
181
190
|
}
|
|
182
|
-
})
|
|
191
|
+
});
|
|
183
192
|
// Структурная модель строится один раз по полному DOM: дальше окно собирается из неё,
|
|
184
193
|
// поэтому отрисовка перестаёт зависеть от того, что сейчас лежит в документе
|
|
185
194
|
this.#buildStructure();
|
|
186
195
|
if (this.#virtual !== undefined) {
|
|
187
196
|
this.#chui_tree_view.style.height = options.height === undefined ? "400px" : options.height;
|
|
188
197
|
// Высота строки одна для вёрстки и для арифметики распорок (в переменном режиме её нет)
|
|
189
|
-
if (!this.#isVariable())
|
|
198
|
+
if (!this.#isVariable())
|
|
199
|
+
this.#chui_tree_view.style.setProperty("--tree_view_row_height", `${this.#rowHeight()}px`);
|
|
190
200
|
this.#chui_tree_view.dataset.virtual = "true";
|
|
191
201
|
main_test.classList.add("tree_view_virtual");
|
|
192
202
|
this.#bindScroll();
|
|
@@ -195,7 +205,7 @@ class TreeView {
|
|
|
195
205
|
}
|
|
196
206
|
this.#applySelection(false);
|
|
197
207
|
this.#syncDraggable();
|
|
198
|
-
this.#chui_tree_view.appendChild(main_test)
|
|
208
|
+
this.#chui_tree_view.appendChild(main_test);
|
|
199
209
|
if (this.#virtual !== undefined) this.#renderVirtual();
|
|
200
210
|
}
|
|
201
211
|
|
|
@@ -285,7 +295,9 @@ class TreeView {
|
|
|
285
295
|
// строки могут быть вне документа, но остаются видимыми по смыслу
|
|
286
296
|
#items() {
|
|
287
297
|
if (this.#virtual === undefined) {
|
|
288
|
-
return [...this.#main_test.querySelectorAll(
|
|
298
|
+
return [...this.#main_test.querySelectorAll("tree_view_button")].filter((button) =>
|
|
299
|
+
this.#isVisibleRow(button)
|
|
300
|
+
);
|
|
289
301
|
}
|
|
290
302
|
return this.#project().map((row) => this.#nodes[row.index].button);
|
|
291
303
|
}
|
|
@@ -308,7 +320,7 @@ class TreeView {
|
|
|
308
320
|
|
|
309
321
|
/** Все кнопки, к которым применим выбор: в виртуальном режиме — по модели, а не по документу */
|
|
310
322
|
#selectionTargets() {
|
|
311
|
-
if (this.#virtual === undefined) return [...this.#main_test.querySelectorAll(
|
|
323
|
+
if (this.#virtual === undefined) return [...this.#main_test.querySelectorAll("tree_view_button")];
|
|
312
324
|
return this.#project().map((row) => this.#nodes[row.index].button);
|
|
313
325
|
}
|
|
314
326
|
|
|
@@ -337,7 +349,8 @@ class TreeView {
|
|
|
337
349
|
for (const node of this.#selectionTargets()) {
|
|
338
350
|
const selected = this.#selected.has(node);
|
|
339
351
|
node.classList.toggle(SELECTED_CLASS, selected);
|
|
340
|
-
if (this.#selection_mode !== TreeView.SELECTION.NONE)
|
|
352
|
+
if (this.#selection_mode !== TreeView.SELECTION.NONE)
|
|
353
|
+
node.setAttribute("aria-selected", selected ? "true" : "false");
|
|
341
354
|
}
|
|
342
355
|
if (!announce || this.#on_select === undefined) return;
|
|
343
356
|
const items = this.getSelected();
|
|
@@ -345,7 +358,9 @@ class TreeView {
|
|
|
345
358
|
}
|
|
346
359
|
|
|
347
360
|
#toggleNode(node) {
|
|
348
|
-
const nodes = this.#selected.has(node)
|
|
361
|
+
const nodes = this.#selected.has(node)
|
|
362
|
+
? [...this.#selected].filter((entry) => entry !== node)
|
|
363
|
+
: [...this.#selected, node];
|
|
349
364
|
this.#anchor = node;
|
|
350
365
|
this.#commitSelection(nodes);
|
|
351
366
|
}
|
|
@@ -416,7 +431,7 @@ class TreeView {
|
|
|
416
431
|
}
|
|
417
432
|
if (target !== undefined && position !== undefined) {
|
|
418
433
|
target.classList.add(DROP_CLASSES[position]);
|
|
419
|
-
this.#drop_target = {node: target, position: position};
|
|
434
|
+
this.#drop_target = { node: target, position: position };
|
|
420
435
|
}
|
|
421
436
|
}
|
|
422
437
|
|
|
@@ -428,7 +443,10 @@ class TreeView {
|
|
|
428
443
|
let reference;
|
|
429
444
|
if (position === TreeView.DROP.INSIDE) {
|
|
430
445
|
const panel = target.nextElementSibling;
|
|
431
|
-
const html =
|
|
446
|
+
const html =
|
|
447
|
+
panel !== null && panel.tagName.toLowerCase() === "tree_view_panel"
|
|
448
|
+
? panel.querySelector("tree_view_html")
|
|
449
|
+
: null;
|
|
432
450
|
if (html === null) return false;
|
|
433
451
|
container = html;
|
|
434
452
|
reference = null;
|
|
@@ -451,7 +469,7 @@ class TreeView {
|
|
|
451
469
|
#descriptor(node) {
|
|
452
470
|
const item = TreeView.#registry.get(node);
|
|
453
471
|
if (item !== undefined) return item;
|
|
454
|
-
return {title: this.#title(node), button: node};
|
|
472
|
+
return { title: this.#title(node), button: node };
|
|
455
473
|
}
|
|
456
474
|
|
|
457
475
|
#title(node) {
|
|
@@ -495,7 +513,7 @@ class TreeView {
|
|
|
495
513
|
parent: parent,
|
|
496
514
|
children: [],
|
|
497
515
|
title: this.#title(button),
|
|
498
|
-
hidden: false
|
|
516
|
+
hidden: false,
|
|
499
517
|
};
|
|
500
518
|
this.#nodes.push(record);
|
|
501
519
|
this.#node_index.set(button, index);
|
|
@@ -529,7 +547,7 @@ class TreeView {
|
|
|
529
547
|
const record = this.#nodes[index];
|
|
530
548
|
if (record === undefined || record.hidden) return 0;
|
|
531
549
|
positions.set(index, rows.length);
|
|
532
|
-
rows.push({index: index, depth: depth});
|
|
550
|
+
rows.push({ index: index, depth: depth });
|
|
533
551
|
const own = variable ? this.#heightOf(index) : this.#rowHeight();
|
|
534
552
|
heights.push(own);
|
|
535
553
|
let size = 1;
|
|
@@ -632,7 +650,7 @@ class TreeView {
|
|
|
632
650
|
#bindScroll() {
|
|
633
651
|
if (this.#scroll_bound) return;
|
|
634
652
|
this.#scroll_bound = true;
|
|
635
|
-
this.#chui_tree_view.addEventListener("scroll", this.#onScroll, {passive: true});
|
|
653
|
+
this.#chui_tree_view.addEventListener("scroll", this.#onScroll, { passive: true });
|
|
636
654
|
}
|
|
637
655
|
|
|
638
656
|
#onScroll = () => {
|
|
@@ -699,9 +717,10 @@ class TreeView {
|
|
|
699
717
|
let scrollTop = Math.min(Math.max(0, this.#chui_tree_view.scrollTop), Math.max(modelBottom, realBottom));
|
|
700
718
|
// Низ окна для переменной высоты — позиция строки под нижней кромкой плюс одна строка,
|
|
701
719
|
// иначе последняя видимая строка выпадала бы из окна
|
|
702
|
-
const windowTo = (top) =>
|
|
703
|
-
|
|
704
|
-
|
|
720
|
+
const windowTo = (top) =>
|
|
721
|
+
variable
|
|
722
|
+
? Math.min(rows.length, positionFor(top + viewport) + 1 + buffer)
|
|
723
|
+
: Math.min(rows.length, Math.ceil((top + viewport) / height) + buffer);
|
|
705
724
|
let from = Math.max(0, positionFor(scrollTop) - buffer);
|
|
706
725
|
let to = windowTo(scrollTop);
|
|
707
726
|
// Список сжался (поиск, сворачивание) — позиция ещё старая и указывает за конец: прижимаем
|
|
@@ -714,8 +733,15 @@ class TreeView {
|
|
|
714
733
|
if (scrollTop !== this.#chui_tree_view.scrollTop) this.#chui_tree_view.scrollTop = scrollTop;
|
|
715
734
|
// Ни окно, ни структура не изменились (прокрутка в пределах строки) — пересобирать
|
|
716
735
|
// нечего: пересборка на каждом кадре только дёргала бы DOM и фокус
|
|
717
|
-
if (
|
|
718
|
-
|
|
736
|
+
if (
|
|
737
|
+
!force &&
|
|
738
|
+
this.#win !== undefined &&
|
|
739
|
+
from === this.#win.from &&
|
|
740
|
+
to === this.#win.to &&
|
|
741
|
+
this.#win_rows === rows.length &&
|
|
742
|
+
this.#win_stamp === this.#stamp
|
|
743
|
+
)
|
|
744
|
+
return;
|
|
719
745
|
// Кто держит фокус: снимок до мутации — после неё `document.activeElement` уже `body`
|
|
720
746
|
const active = document.activeElement;
|
|
721
747
|
const activeIndex = active instanceof Element ? this.#node_index.get(active) : undefined;
|
|
@@ -745,10 +771,10 @@ class TreeView {
|
|
|
745
771
|
return node;
|
|
746
772
|
};
|
|
747
773
|
// Сколько занимает пропущенная ветка: в переменном режиме — суммарная высота её поддерева
|
|
748
|
-
const subtreePx = (index) => variable ?
|
|
774
|
+
const subtreePx = (index) => (variable ? this.#subtree_heights.get(index) || 0 : sizeOf(index) * height);
|
|
749
775
|
const fill = (container, parentIndex) => {
|
|
750
776
|
// Дети берутся из проекции: скрытое поиском и свёрнутое в неё уже не попадает
|
|
751
|
-
const children = parentIndex === -1 ? this.#root_rows :
|
|
777
|
+
const children = parentIndex === -1 ? this.#root_rows : this.#child_rows.get(parentIndex) || [];
|
|
752
778
|
const parts = [];
|
|
753
779
|
let gap = 0;
|
|
754
780
|
for (const index of children) {
|
|
@@ -788,7 +814,7 @@ class TreeView {
|
|
|
788
814
|
this.#restoreFocus(keepIndex);
|
|
789
815
|
this.#applySelection(false);
|
|
790
816
|
this.#syncDraggable();
|
|
791
|
-
this.#win = {from: from, to: to};
|
|
817
|
+
this.#win = { from: from, to: to };
|
|
792
818
|
this.#win_stamp = this.#stamp;
|
|
793
819
|
this.#win_rows = rows.length;
|
|
794
820
|
}
|
|
@@ -818,14 +844,18 @@ class TreeView {
|
|
|
818
844
|
if (index === undefined) return;
|
|
819
845
|
const record = this.#nodes[index];
|
|
820
846
|
if (record === undefined || document.activeElement === record.button) return;
|
|
821
|
-
record.button.focus({preventScroll: true});
|
|
847
|
+
record.button.focus({ preventScroll: true });
|
|
822
848
|
}
|
|
823
849
|
|
|
824
850
|
/** Что сейчас в окне: {from, to} по плоскому списку (для стендов и приложений) */
|
|
825
|
-
getVirtualWindow() {
|
|
851
|
+
getVirtualWindow() {
|
|
852
|
+
return this.#win;
|
|
853
|
+
}
|
|
826
854
|
|
|
827
855
|
/** Переменная высота строк включена (`rowHeight`-функция или `variableHeight`) */
|
|
828
|
-
isVariableHeight() {
|
|
856
|
+
isVariableHeight() {
|
|
857
|
+
return this.#isVariable();
|
|
858
|
+
}
|
|
829
859
|
|
|
830
860
|
/** Высота строки с индексом модели: из данных, из замеров или заданная */
|
|
831
861
|
getRowHeightAt(index = Number()) {
|
|
@@ -856,7 +886,7 @@ class TreeView {
|
|
|
856
886
|
if (place === undefined) return;
|
|
857
887
|
// В переменном режиме верх и высота строки берутся из смещений проекции
|
|
858
888
|
const variable = this.#isVariable();
|
|
859
|
-
const height = variable ?
|
|
889
|
+
const height = variable ? this.#row_heights[place] || this.#rowHeight() : this.#rowHeight();
|
|
860
890
|
const viewport = Math.max(1, this.#chui_tree_view.clientHeight);
|
|
861
891
|
const top = variable ? this.#offsets[place] : place * height;
|
|
862
892
|
const scrollTop = this.#chui_tree_view.scrollTop;
|
|
@@ -921,17 +951,23 @@ class TreeView {
|
|
|
921
951
|
}
|
|
922
952
|
|
|
923
953
|
// === ПОИСК ===
|
|
924
|
-
getSearchQuery() {
|
|
925
|
-
|
|
954
|
+
getSearchQuery() {
|
|
955
|
+
return this.#query;
|
|
956
|
+
}
|
|
957
|
+
isSearching() {
|
|
958
|
+
return this.#query !== "";
|
|
959
|
+
}
|
|
926
960
|
/** Совпадения поиска (описания из фабрик), в порядке показа */
|
|
927
|
-
getMatches() {
|
|
961
|
+
getMatches() {
|
|
962
|
+
return [...this.#matches].map((node) => this.#descriptor(node));
|
|
963
|
+
}
|
|
928
964
|
|
|
929
965
|
/**
|
|
930
966
|
* Показать только подходящие узлы и цепочку их предков; остальное убирается (`display: none`),
|
|
931
967
|
* поэтому навигация и выбор дальше работают по видимому. `autoExpand` раскрывает предков
|
|
932
968
|
* (без него совпадения внутри свёрнутых веток остаются скрытыми), `highlight` подсвечивает совпадения.
|
|
933
969
|
*/
|
|
934
|
-
search(query = String(), options = {autoExpand: true, highlight: true}) {
|
|
970
|
+
search(query = String(), options = { autoExpand: true, highlight: true }) {
|
|
935
971
|
this.clearSearch();
|
|
936
972
|
const text = String(query).trim().toLocaleLowerCase();
|
|
937
973
|
if (text === "") return this;
|
|
@@ -939,7 +975,7 @@ class TreeView {
|
|
|
939
975
|
// В виртуальном режиме в документе только окно — ищем и скрываем по модели
|
|
940
976
|
if (this.#virtual !== undefined) {
|
|
941
977
|
this.#searchModel(text, options);
|
|
942
|
-
this.#announce(I18n.t("treeView.found", {count: this.#matches.size}));
|
|
978
|
+
this.#announce(I18n.t("treeView.found", { count: this.#matches.size }));
|
|
943
979
|
return this;
|
|
944
980
|
}
|
|
945
981
|
const buttons = [...this.#main_test.querySelectorAll("tree_view_button")];
|
|
@@ -972,7 +1008,7 @@ class TreeView {
|
|
|
972
1008
|
if (options.highlight !== false) for (const node of this.#matches) node.classList.add("tree_view_button_match");
|
|
973
1009
|
this.#touch();
|
|
974
1010
|
if (this.#virtual !== undefined) this.#renderVirtual();
|
|
975
|
-
this.#announce(I18n.t("treeView.found", {count: this.#matches.size}));
|
|
1011
|
+
this.#announce(I18n.t("treeView.found", { count: this.#matches.size }));
|
|
976
1012
|
return this;
|
|
977
1013
|
}
|
|
978
1014
|
|
|
@@ -1000,7 +1036,9 @@ class TreeView {
|
|
|
1000
1036
|
return this;
|
|
1001
1037
|
}
|
|
1002
1038
|
|
|
1003
|
-
collapse(target) {
|
|
1039
|
+
collapse(target) {
|
|
1040
|
+
return this.expand(target, false);
|
|
1041
|
+
}
|
|
1004
1042
|
|
|
1005
1043
|
/** Раскрыть всё: в виртуальном режиме — единственный способ, строки вне окна не кликнуть */
|
|
1006
1044
|
expandAll() {
|
|
@@ -1014,7 +1052,8 @@ class TreeView {
|
|
|
1014
1052
|
}
|
|
1015
1053
|
|
|
1016
1054
|
getExpanded() {
|
|
1017
|
-
return this.#nodes
|
|
1055
|
+
return this.#nodes
|
|
1056
|
+
.filter((record) => record.button.getAttribute("aria-expanded") === "true")
|
|
1018
1057
|
.map((record) => this.#descriptor(record.button));
|
|
1019
1058
|
}
|
|
1020
1059
|
|
|
@@ -1045,14 +1084,16 @@ class TreeView {
|
|
|
1045
1084
|
}
|
|
1046
1085
|
}
|
|
1047
1086
|
for (const record of this.#nodes) record.hidden = !visible.has(record.index);
|
|
1048
|
-
if (options.highlight !== false)
|
|
1087
|
+
if (options.highlight !== false)
|
|
1088
|
+
for (const button of this.#matches) button.classList.add("tree_view_button_match");
|
|
1049
1089
|
this.#touch();
|
|
1050
1090
|
this.#renderVirtual();
|
|
1051
1091
|
}
|
|
1052
1092
|
|
|
1053
1093
|
/** Снять фильтр поиска: возвращает все узлы и снимает подсветку */
|
|
1054
1094
|
clearSearch() {
|
|
1055
|
-
for (const node of this.#main_test.querySelectorAll("." + TreeView.#HIDDEN_CLASS))
|
|
1095
|
+
for (const node of this.#main_test.querySelectorAll("." + TreeView.#HIDDEN_CLASS))
|
|
1096
|
+
node.classList.remove(TreeView.#HIDDEN_CLASS);
|
|
1056
1097
|
for (const node of this.#matches) node.classList.remove("tree_view_button_match");
|
|
1057
1098
|
for (const record of this.#nodes) record.hidden = false;
|
|
1058
1099
|
const wasSearching = this.#query !== "";
|
|
@@ -1067,7 +1108,9 @@ class TreeView {
|
|
|
1067
1108
|
// Статус поиска читает скринридер (узла нет, пока поиск не использовался)
|
|
1068
1109
|
#announce(text = String()) {
|
|
1069
1110
|
if (this.#status === undefined) {
|
|
1070
|
-
this.#status = Object.assign(document.createElement("chui_visually_hidden"), {
|
|
1111
|
+
this.#status = Object.assign(document.createElement("chui_visually_hidden"), {
|
|
1112
|
+
className: "chui_visually_hidden",
|
|
1113
|
+
});
|
|
1071
1114
|
this.#status.setAttribute("role", "status");
|
|
1072
1115
|
this.#status.setAttribute("aria-live", "polite");
|
|
1073
1116
|
this.#main_test.appendChild(this.#status);
|
|
@@ -1106,7 +1149,7 @@ class TreeView {
|
|
|
1106
1149
|
|
|
1107
1150
|
#onNavigationKeyDown = (event) => {
|
|
1108
1151
|
const items = this.#items();
|
|
1109
|
-
const current = items.findIndex(item => item === document.activeElement);
|
|
1152
|
+
const current = items.findIndex((item) => item === document.activeElement);
|
|
1110
1153
|
if (current === -1) return;
|
|
1111
1154
|
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
1112
1155
|
if (event.altKey && this.#draggable) {
|
|
@@ -1126,7 +1169,7 @@ class TreeView {
|
|
|
1126
1169
|
document.activeElement.click();
|
|
1127
1170
|
}
|
|
1128
1171
|
} else if (event.key === "ArrowLeft") {
|
|
1129
|
-
const parent = document.activeElement.parentElement.closest(
|
|
1172
|
+
const parent = document.activeElement.parentElement.closest("tree_view_button[aria-expanded]");
|
|
1130
1173
|
if (document.activeElement.getAttribute("aria-expanded") === "true") {
|
|
1131
1174
|
event.preventDefault();
|
|
1132
1175
|
document.activeElement.click();
|
|
@@ -1200,20 +1243,20 @@ class TreeView {
|
|
|
1200
1243
|
};
|
|
1201
1244
|
|
|
1202
1245
|
static Button(options = { title: String(), listener: () => {} }) {
|
|
1203
|
-
let button = document.createElement(
|
|
1246
|
+
let button = document.createElement("tree_view_button");
|
|
1204
1247
|
if (options.listener !== undefined) button.addEventListener("click", options.listener);
|
|
1205
1248
|
makeKeyboardButton(button, "treeitem");
|
|
1206
|
-
let button_text = document.createElement(
|
|
1249
|
+
let button_text = document.createElement("tree_view_button_text");
|
|
1207
1250
|
button_text.innerHTML = options.title;
|
|
1208
|
-
button.appendChild(button_text)
|
|
1209
|
-
const item = {title: options.title, button: button};
|
|
1251
|
+
button.appendChild(button_text);
|
|
1252
|
+
const item = { title: options.title, button: button };
|
|
1210
1253
|
TreeView.#registry.set(button, item);
|
|
1211
1254
|
return item;
|
|
1212
1255
|
}
|
|
1213
1256
|
|
|
1214
1257
|
static ExpandButton(options = { title: String(), subButtons: [], components: [], lazySubButtons: undefined }) {
|
|
1215
|
-
let button = document.createElement(
|
|
1216
|
-
let panel = document.createElement(
|
|
1258
|
+
let button = document.createElement("tree_view_button");
|
|
1259
|
+
let panel = document.createElement("tree_view_panel");
|
|
1217
1260
|
// Раскрытая панель переходит с фиксированной высоты на max-content: иначе высота
|
|
1218
1261
|
// остаётся «замороженной» после первого раскрытия
|
|
1219
1262
|
const transitionEndEvent = (event) => {
|
|
@@ -1224,29 +1267,29 @@ class TreeView {
|
|
|
1224
1267
|
button.setAttribute("aria-controls", panel.id);
|
|
1225
1268
|
button.setAttribute("aria-expanded", "false");
|
|
1226
1269
|
makeKeyboardButton(button, "treeitem");
|
|
1227
|
-
button.addEventListener(
|
|
1270
|
+
button.addEventListener("click", () => {
|
|
1228
1271
|
buildSubs();
|
|
1229
|
-
const open = !button.classList.contains(
|
|
1230
|
-
button.classList.toggle(
|
|
1272
|
+
const open = !button.classList.contains("tree_view_button_active");
|
|
1273
|
+
button.classList.toggle("tree_view_button_active", open);
|
|
1231
1274
|
// Стрелка поворачивается по aria-expanded (styles.css) — состояние одно
|
|
1232
1275
|
button.setAttribute("aria-expanded", open ? "true" : "false");
|
|
1233
1276
|
if (open) {
|
|
1234
1277
|
panel.style.maxHeight = panel.scrollHeight + "px";
|
|
1235
|
-
panel.addEventListener(
|
|
1278
|
+
panel.addEventListener("transitionend", transitionEndEvent);
|
|
1236
1279
|
} else {
|
|
1237
1280
|
// Сначала фиксируем текущую высоту, иначе сжатие не анимируется
|
|
1238
1281
|
panel.style.maxHeight = panel.scrollHeight + "px";
|
|
1239
1282
|
void panel.offsetHeight;
|
|
1240
1283
|
panel.style.maxHeight = "0px";
|
|
1241
1284
|
}
|
|
1242
|
-
})
|
|
1243
|
-
let button_text = document.createElement(
|
|
1285
|
+
});
|
|
1286
|
+
let button_text = document.createElement("tree_view_button_text");
|
|
1244
1287
|
button_text.innerHTML = options.title;
|
|
1245
|
-
button.appendChild(button_text)
|
|
1246
|
-
let barrow = document.createElement(
|
|
1288
|
+
button.appendChild(button_text);
|
|
1289
|
+
let barrow = document.createElement("tree_view_button_arrow");
|
|
1247
1290
|
barrow.innerHTML = new Icon(Icons.HARDWARE.KEYBOARD_ARROW_DOWN).getHTML();
|
|
1248
|
-
button.appendChild(barrow)
|
|
1249
|
-
let html = document.createElement(
|
|
1291
|
+
button.appendChild(barrow);
|
|
1292
|
+
let html = document.createElement("tree_view_html");
|
|
1250
1293
|
// Ленивое поддерево: содержимое строится один раз — при первом раскрытии,
|
|
1251
1294
|
// поэтому большое дерево не занимает DOM заранее (lazySubButtons: () => [...])
|
|
1252
1295
|
let subs_built = false;
|
|
@@ -1254,34 +1297,34 @@ class TreeView {
|
|
|
1254
1297
|
if (subs_built) return;
|
|
1255
1298
|
subs_built = true;
|
|
1256
1299
|
if (options.subButtons !== undefined) {
|
|
1257
|
-
options.subButtons.forEach(sub => {
|
|
1258
|
-
html.appendChild(sub.button)
|
|
1300
|
+
options.subButtons.forEach((sub) => {
|
|
1301
|
+
html.appendChild(sub.button);
|
|
1259
1302
|
if (sub.panel !== undefined) {
|
|
1260
|
-
html.appendChild(sub.panel)
|
|
1303
|
+
html.appendChild(sub.panel);
|
|
1261
1304
|
}
|
|
1262
|
-
})
|
|
1305
|
+
});
|
|
1263
1306
|
}
|
|
1264
1307
|
if (options.components !== undefined) {
|
|
1265
|
-
options.components.forEach(sub => {
|
|
1266
|
-
html.appendChild(sub.set())
|
|
1267
|
-
})
|
|
1308
|
+
options.components.forEach((sub) => {
|
|
1309
|
+
html.appendChild(sub.set());
|
|
1310
|
+
});
|
|
1268
1311
|
}
|
|
1269
1312
|
if (options.lazySubButtons !== undefined) {
|
|
1270
|
-
options.lazySubButtons().forEach(sub => {
|
|
1271
|
-
html.appendChild(sub.button)
|
|
1313
|
+
options.lazySubButtons().forEach((sub) => {
|
|
1314
|
+
html.appendChild(sub.button);
|
|
1272
1315
|
if (sub.panel !== undefined) {
|
|
1273
|
-
html.appendChild(sub.panel)
|
|
1316
|
+
html.appendChild(sub.panel);
|
|
1274
1317
|
}
|
|
1275
|
-
})
|
|
1318
|
+
});
|
|
1276
1319
|
}
|
|
1277
1320
|
};
|
|
1278
1321
|
// Без ленивой фабрики поведение прежнее — строим сразу
|
|
1279
1322
|
if (options.lazySubButtons === undefined) buildSubs();
|
|
1280
|
-
panel.appendChild(html)
|
|
1281
|
-
const item = {title: options.title, button: button, panel: panel};
|
|
1323
|
+
panel.appendChild(html);
|
|
1324
|
+
const item = { title: options.title, button: button, panel: panel };
|
|
1282
1325
|
TreeView.#registry.set(button, item);
|
|
1283
1326
|
return item;
|
|
1284
1327
|
}
|
|
1285
1328
|
}
|
|
1286
1329
|
|
|
1287
|
-
exports.TreeView = TreeView
|
|
1330
|
+
exports.TreeView = TreeView;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {setStyles, optionOr} = require(
|
|
1
|
+
const { setStyles, optionOr } = require("../../modules/chui_functions");
|
|
2
2
|
|
|
3
3
|
/** Схемы источника картинки: остальное считается локальным путём и читается как data URL */
|
|
4
4
|
const URL_SCHEMES = ["data:", "http:", "https:", "file:", "blob:"];
|
|
@@ -32,7 +32,7 @@ const URL_SCHEMES = ["data:", "http:", "https:", "file:", "blob:"];
|
|
|
32
32
|
*/
|
|
33
33
|
class Watermark {
|
|
34
34
|
static #counter = 0;
|
|
35
|
-
static #DEFAULT_GAP = {x: 100, y: 100};
|
|
35
|
+
static #DEFAULT_GAP = { x: 100, y: 100 };
|
|
36
36
|
static #DEFAULT_ROTATE = -22;
|
|
37
37
|
static #DEFAULT_OPACITY = 0.15;
|
|
38
38
|
static #DEFAULT_FONT_SIZE = 16;
|
|
@@ -45,19 +45,27 @@ class Watermark {
|
|
|
45
45
|
#text = undefined;
|
|
46
46
|
#image = undefined;
|
|
47
47
|
#image_node = undefined;
|
|
48
|
-
#font = {size: Watermark.#DEFAULT_FONT_SIZE, weight: 400, family: undefined, color: undefined};
|
|
48
|
+
#font = { size: Watermark.#DEFAULT_FONT_SIZE, weight: 400, family: undefined, color: undefined };
|
|
49
49
|
#rotate = Watermark.#DEFAULT_ROTATE;
|
|
50
|
-
#gap = {...Watermark.#DEFAULT_GAP};
|
|
50
|
+
#gap = { ...Watermark.#DEFAULT_GAP };
|
|
51
51
|
#opacity = Watermark.#DEFAULT_OPACITY;
|
|
52
52
|
#z_index = Watermark.#DEFAULT_Z_INDEX;
|
|
53
53
|
#scheme = undefined;
|
|
54
54
|
#destroyed = false;
|
|
55
55
|
|
|
56
|
-
constructor(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
constructor(
|
|
57
|
+
options = {
|
|
58
|
+
id: String(),
|
|
59
|
+
text: String(),
|
|
60
|
+
image: String(),
|
|
61
|
+
font: Object(),
|
|
62
|
+
rotate: Number(),
|
|
63
|
+
gap: Object(),
|
|
64
|
+
opacity: Number(),
|
|
65
|
+
zIndex: Number(),
|
|
66
|
+
components: [],
|
|
67
|
+
}
|
|
68
|
+
) {
|
|
61
69
|
setStyles(__dirname + "/styles.css", "chUiJS_Watermark");
|
|
62
70
|
Watermark.#counter += 1;
|
|
63
71
|
this.#id = optionOr(options.id, `chui_watermark_${Watermark.#counter}`);
|
|
@@ -98,7 +106,7 @@ class Watermark {
|
|
|
98
106
|
|
|
99
107
|
/** Настройки метки: `{size, weight, family, color}` */
|
|
100
108
|
getFont() {
|
|
101
|
-
return {...this.#font};
|
|
109
|
+
return { ...this.#font };
|
|
102
110
|
}
|
|
103
111
|
|
|
104
112
|
getRotate() {
|
|
@@ -107,7 +115,7 @@ class Watermark {
|
|
|
107
115
|
|
|
108
116
|
/** Шаг между метками */
|
|
109
117
|
getGap() {
|
|
110
|
-
return {...this.#gap};
|
|
118
|
+
return { ...this.#gap };
|
|
111
119
|
}
|
|
112
120
|
|
|
113
121
|
getOpacity() {
|
|
@@ -245,12 +253,12 @@ class Watermark {
|
|
|
245
253
|
}
|
|
246
254
|
|
|
247
255
|
static #normalizeGap(gap = Object()) {
|
|
248
|
-
const source = Array.isArray(gap) ? {x: gap[0], y: gap[1]} : gap;
|
|
256
|
+
const source = Array.isArray(gap) ? { x: gap[0], y: gap[1] } : gap;
|
|
249
257
|
const x = Number(source === undefined ? undefined : source.x);
|
|
250
258
|
const y = Number(source === undefined ? undefined : source.y);
|
|
251
259
|
return {
|
|
252
260
|
x: Number.isFinite(x) && x > 0 ? x : Watermark.#DEFAULT_GAP.x,
|
|
253
|
-
y: Number.isFinite(y) && y > 0 ? y : Watermark.#DEFAULT_GAP.y
|
|
261
|
+
y: Number.isFinite(y) && y > 0 ? y : Watermark.#DEFAULT_GAP.y,
|
|
254
262
|
};
|
|
255
263
|
}
|
|
256
264
|
|
|
@@ -265,7 +273,7 @@ class Watermark {
|
|
|
265
273
|
weight: this.#font.weight,
|
|
266
274
|
// Шрифт и цвет палитры берём из вычисленного стиля: канва наследование не умеет
|
|
267
275
|
family: optionOr(this.#font.family, style.fontFamily),
|
|
268
|
-
color: optionOr(this.#font.color, style.color)
|
|
276
|
+
color: optionOr(this.#font.color, style.color),
|
|
269
277
|
};
|
|
270
278
|
}
|
|
271
279
|
|
|
@@ -283,7 +291,7 @@ class Watermark {
|
|
|
283
291
|
return this;
|
|
284
292
|
}
|
|
285
293
|
const pixel_ratio = typeof window !== "undefined" && window.devicePixelRatio > 0 ? window.devicePixelRatio : 1;
|
|
286
|
-
const tile = {x: this.#gap.x, y: this.#gap.y};
|
|
294
|
+
const tile = { x: this.#gap.x, y: this.#gap.y };
|
|
287
295
|
// Плитка = две метки по каждой оси: на стыках соседних плиток рисунок сходится
|
|
288
296
|
const canvas = document.createElement("canvas");
|
|
289
297
|
canvas.width = Math.round(tile.x * 2 * pixel_ratio);
|
|
@@ -330,4 +338,4 @@ class Watermark {
|
|
|
330
338
|
}
|
|
331
339
|
}
|
|
332
340
|
|
|
333
|
-
exports.Watermark = Watermark
|
|
341
|
+
exports.Watermark = Watermark;
|