suneditor 3.0.0-beta.2 → 3.0.0-beta.20
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/CONTRIBUTING.md +186 -184
- package/LICENSE +21 -21
- package/README.md +157 -180
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +126 -123
- package/src/assets/design/color.css +131 -121
- package/src/assets/design/index.css +3 -3
- package/src/assets/design/size.css +37 -35
- package/src/assets/design/typography.css +37 -37
- package/src/assets/icons/defaultIcons.js +247 -232
- package/src/assets/suneditor-contents.css +779 -778
- package/src/assets/suneditor.css +43 -35
- package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
- package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
- package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
- package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
- package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
- package/src/core/base/eventManager.js +1550 -1484
- package/src/core/base/history.js +355 -355
- package/src/core/class/char.js +163 -162
- package/src/core/class/component.js +856 -842
- package/src/core/class/format.js +3433 -3422
- package/src/core/class/html.js +1927 -1890
- package/src/core/class/menu.js +357 -346
- package/src/core/class/nodeTransform.js +424 -424
- package/src/core/class/offset.js +858 -891
- package/src/core/class/selection.js +710 -620
- package/src/core/class/shortcuts.js +98 -98
- package/src/core/class/toolbar.js +438 -430
- package/src/core/class/ui.js +424 -422
- package/src/core/class/viewer.js +750 -750
- package/src/core/editor.js +1810 -1708
- package/src/core/section/actives.js +268 -241
- package/src/core/section/constructor.js +1348 -1661
- package/src/core/section/context.js +102 -102
- package/src/core/section/documentType.js +582 -561
- package/src/core/section/options.js +367 -0
- package/src/core/util/instanceCheck.js +59 -0
- package/src/editorInjector/_classes.js +36 -36
- package/src/editorInjector/_core.js +92 -92
- package/src/editorInjector/index.js +75 -75
- package/src/events.js +634 -622
- package/src/helper/clipboard.js +59 -59
- package/src/helper/converter.js +586 -564
- package/src/helper/dom/domCheck.js +304 -304
- package/src/helper/dom/domQuery.js +677 -669
- package/src/helper/dom/domUtils.js +618 -557
- package/src/helper/dom/index.js +12 -12
- package/src/helper/env.js +249 -240
- package/src/helper/index.js +25 -25
- package/src/helper/keyCodeMap.js +183 -183
- package/src/helper/numbers.js +72 -72
- package/src/helper/unicode.js +47 -47
- package/src/langs/ckb.js +231 -231
- package/src/langs/cs.js +231 -231
- package/src/langs/da.js +231 -231
- package/src/langs/de.js +231 -231
- package/src/langs/en.js +230 -230
- package/src/langs/es.js +231 -231
- package/src/langs/fa.js +231 -231
- package/src/langs/fr.js +231 -231
- package/src/langs/he.js +231 -231
- package/src/langs/hu.js +230 -230
- package/src/langs/index.js +28 -28
- package/src/langs/it.js +231 -231
- package/src/langs/ja.js +230 -230
- package/src/langs/km.js +230 -230
- package/src/langs/ko.js +230 -230
- package/src/langs/lv.js +231 -231
- package/src/langs/nl.js +231 -231
- package/src/langs/pl.js +231 -231
- package/src/langs/pt_br.js +231 -231
- package/src/langs/ro.js +231 -231
- package/src/langs/ru.js +231 -231
- package/src/langs/se.js +231 -231
- package/src/langs/tr.js +231 -231
- package/src/langs/uk.js +231 -231
- package/src/langs/ur.js +231 -231
- package/src/langs/zh_cn.js +231 -231
- package/src/modules/ApiManager.js +191 -191
- package/src/modules/Browser.js +669 -667
- package/src/modules/ColorPicker.js +364 -362
- package/src/modules/Controller.js +474 -454
- package/src/modules/Figure.js +1620 -1617
- package/src/modules/FileManager.js +359 -359
- package/src/modules/HueSlider.js +577 -565
- package/src/modules/Modal.js +346 -346
- package/src/modules/ModalAnchorEditor.js +643 -643
- package/src/modules/SelectMenu.js +549 -549
- package/src/modules/_DragHandle.js +17 -17
- package/src/modules/index.js +14 -14
- package/src/plugins/browser/audioGallery.js +83 -83
- package/src/plugins/browser/fileBrowser.js +103 -103
- package/src/plugins/browser/fileGallery.js +83 -83
- package/src/plugins/browser/imageGallery.js +81 -81
- package/src/plugins/browser/videoGallery.js +103 -103
- package/src/plugins/command/blockquote.js +61 -60
- package/src/plugins/command/exportPDF.js +134 -134
- package/src/plugins/command/fileUpload.js +456 -456
- package/src/plugins/command/list_bulleted.js +149 -148
- package/src/plugins/command/list_numbered.js +152 -151
- package/src/plugins/dropdown/align.js +157 -155
- package/src/plugins/dropdown/backgroundColor.js +108 -104
- package/src/plugins/dropdown/font.js +141 -137
- package/src/plugins/dropdown/fontColor.js +109 -105
- package/src/plugins/dropdown/formatBlock.js +170 -178
- package/src/plugins/dropdown/hr.js +152 -152
- package/src/plugins/dropdown/layout.js +83 -83
- package/src/plugins/dropdown/lineHeight.js +131 -130
- package/src/plugins/dropdown/list.js +123 -122
- package/src/plugins/dropdown/paragraphStyle.js +138 -138
- package/src/plugins/dropdown/table.js +4110 -4000
- package/src/plugins/dropdown/template.js +83 -83
- package/src/plugins/dropdown/textStyle.js +149 -149
- package/src/plugins/field/mention.js +242 -242
- package/src/plugins/index.js +120 -120
- package/src/plugins/input/fontSize.js +414 -410
- package/src/plugins/input/pageNavigator.js +71 -70
- package/src/plugins/modal/audio.js +677 -677
- package/src/plugins/modal/drawing.js +537 -531
- package/src/plugins/modal/embed.js +886 -886
- package/src/plugins/modal/image.js +1377 -1376
- package/src/plugins/modal/link.js +248 -240
- package/src/plugins/modal/math.js +563 -563
- package/src/plugins/modal/video.js +1226 -1226
- package/src/plugins/popup/anchor.js +224 -222
- package/src/suneditor.js +114 -107
- package/src/themes/dark.css +132 -122
- package/src/typedef.js +132 -130
- package/types/assets/icons/defaultIcons.d.ts +8 -0
- package/types/core/base/eventManager.d.ts +29 -4
- package/types/core/class/char.d.ts +2 -1
- package/types/core/class/component.d.ts +1 -2
- package/types/core/class/format.d.ts +8 -1
- package/types/core/class/html.d.ts +8 -0
- package/types/core/class/menu.d.ts +8 -0
- package/types/core/class/offset.d.ts +24 -26
- package/types/core/class/selection.d.ts +2 -0
- package/types/core/class/toolbar.d.ts +6 -0
- package/types/core/class/ui.d.ts +1 -1
- package/types/core/editor.d.ts +34 -12
- package/types/core/section/constructor.d.ts +5 -638
- package/types/core/section/documentType.d.ts +12 -2
- package/types/core/section/options.d.ts +740 -0
- package/types/core/util/instanceCheck.d.ts +50 -0
- package/types/editorInjector/_core.d.ts +5 -5
- package/types/editorInjector/index.d.ts +2 -2
- package/types/events.d.ts +2 -0
- package/types/helper/converter.d.ts +9 -0
- package/types/helper/dom/domQuery.d.ts +5 -5
- package/types/helper/dom/domUtils.d.ts +8 -0
- package/types/helper/env.d.ts +6 -1
- package/types/helper/index.d.ts +4 -1
- package/types/index.d.ts +122 -120
- package/types/langs/_Lang.d.ts +194 -194
- package/types/modules/ColorPicker.d.ts +5 -1
- package/types/modules/Controller.d.ts +8 -4
- package/types/modules/Figure.d.ts +2 -1
- package/types/modules/HueSlider.d.ts +4 -1
- package/types/modules/SelectMenu.d.ts +1 -1
- package/types/plugins/command/blockquote.d.ts +1 -0
- package/types/plugins/command/list_bulleted.d.ts +1 -0
- package/types/plugins/command/list_numbered.d.ts +1 -0
- package/types/plugins/dropdown/align.d.ts +1 -0
- package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
- package/types/plugins/dropdown/font.d.ts +1 -0
- package/types/plugins/dropdown/fontColor.d.ts +1 -0
- package/types/plugins/dropdown/formatBlock.d.ts +3 -2
- package/types/plugins/dropdown/lineHeight.d.ts +1 -0
- package/types/plugins/dropdown/list.d.ts +1 -0
- package/types/plugins/dropdown/table.d.ts +6 -0
- package/types/plugins/input/fontSize.d.ts +1 -0
- package/types/plugins/modal/drawing.d.ts +4 -0
- package/types/plugins/modal/link.d.ts +32 -15
- package/types/suneditor.d.ts +13 -9
- package/types/typedef.d.ts +8 -0
|
@@ -1,549 +1,549 @@
|
|
|
1
|
-
import CoreInjector from '../editorInjector/_core';
|
|
2
|
-
import { dom, env, keyCodeMap } from '../helper';
|
|
3
|
-
|
|
4
|
-
const MENU_MIN_HEIGHT = 38;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {Object} SelectMenuParams
|
|
8
|
-
* @property {string} position Position of the select menu, specified as "[left|right]-[middle|top|bottom]" or "[top|bottom]-[center|left|right]"
|
|
9
|
-
* @property {boolean} [checkList=false] Flag to determine if the checklist is enabled (true or false)
|
|
10
|
-
* @property {"rtl" | "ltr"} [dir="ltr"] Optional text direction: "rtl" for right-to-left, "ltr" for left-to-right
|
|
11
|
-
* @property {number} [splitNum=0] Optional split number for horizontal positioning; defines how many items per row
|
|
12
|
-
* @property {() => void=} openMethod Optional method to call when the menu is opened
|
|
13
|
-
* @property {() => void=} closeMethod Optional method to call when the menu is closed
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @class
|
|
18
|
-
* @description Creates a select menu
|
|
19
|
-
*/
|
|
20
|
-
class SelectMenu extends CoreInjector {
|
|
21
|
-
/**
|
|
22
|
-
* @constructor
|
|
23
|
-
* @param {*} inst The instance object that called the constructor.
|
|
24
|
-
* @param {SelectMenuParams} params Select menu options
|
|
25
|
-
*/
|
|
26
|
-
constructor(inst, params) {
|
|
27
|
-
// plugin bisic properties
|
|
28
|
-
super(inst.editor);
|
|
29
|
-
|
|
30
|
-
// members
|
|
31
|
-
this.kink = inst.constructor.key || inst.constructor.name;
|
|
32
|
-
this.inst = inst;
|
|
33
|
-
const positionItems = params.position.split('-');
|
|
34
|
-
this.form = null;
|
|
35
|
-
this.items = [];
|
|
36
|
-
/** @type {HTMLLIElement[]} */
|
|
37
|
-
this.menus = null;
|
|
38
|
-
this.menuLen = 0;
|
|
39
|
-
this.index = -1;
|
|
40
|
-
this.item = null;
|
|
41
|
-
this.isOpen = false;
|
|
42
|
-
this.checkList = !!params.checkList;
|
|
43
|
-
this.position = positionItems[0];
|
|
44
|
-
this.subPosition = positionItems[1];
|
|
45
|
-
this._dirPosition = /^(left|right)$/.test(this.position) ? (this.position === 'left' ? 'right' : 'left') : this.position;
|
|
46
|
-
this._dirSubPosition = /^(left|right)$/.test(this.subPosition) ? (this.subPosition === 'left' ? 'right' : 'left') : this.subPosition;
|
|
47
|
-
this._textDirDiff = params.dir === 'ltr' ? false : params.dir === 'rtl' ? true : null;
|
|
48
|
-
this.splitNum = params.splitNum || 0;
|
|
49
|
-
this.horizontal = !!this.splitNum;
|
|
50
|
-
this.openMethod = params.openMethod;
|
|
51
|
-
this.closeMethod = params.closeMethod;
|
|
52
|
-
this._refer = null;
|
|
53
|
-
this._keydownTarget = null;
|
|
54
|
-
this._selectMethod = null;
|
|
55
|
-
this._bindClose_key = null;
|
|
56
|
-
this._bindClose_mousedown = null;
|
|
57
|
-
this._bindClose_click = null;
|
|
58
|
-
this._closeSignal = false;
|
|
59
|
-
this.__events = null;
|
|
60
|
-
this.__eventHandlers = {
|
|
61
|
-
mousedown: this.#OnMousedown_list.bind(this),
|
|
62
|
-
mousemove: this.#OnMouseMove_list.bind(this),
|
|
63
|
-
click: this.#OnClick_list.bind(this),
|
|
64
|
-
keydown: this.#OnKeyDown_refer.bind(this)
|
|
65
|
-
};
|
|
66
|
-
this.__globalEventHandlers = { keydown: this.#CloseListener_key.bind(this), mousedown: this.#CloseListener_mousedown.bind(this), click: this.#CloseListener_click.bind(this) };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* @description Creates the select menu items.
|
|
71
|
-
* @param {Array<string>|__se__NodeCollection} items - Command list of selectable items.
|
|
72
|
-
* @param {Array<string>|__se__NodeCollection} [menus] - Optional list of menu display elements; defaults to `items`.
|
|
73
|
-
*/
|
|
74
|
-
create(items, menus) {
|
|
75
|
-
this.form.firstElementChild.innerHTML = '';
|
|
76
|
-
menus = menus || items;
|
|
77
|
-
let html = '';
|
|
78
|
-
for (let i = 0, len = menus.length; i < len; i++) {
|
|
79
|
-
if (i > 0 && i % this.splitNum === 0) {
|
|
80
|
-
this._createFormat(html);
|
|
81
|
-
html = '';
|
|
82
|
-
}
|
|
83
|
-
html += `<li class="se-select-item" data-index="${i}">${typeof menus[i] === 'string' ? menus[i] : /** @type {HTMLElement} */ (menus[i]).outerHTML}</li>`;
|
|
84
|
-
}
|
|
85
|
-
this._createFormat(html);
|
|
86
|
-
|
|
87
|
-
this.items = /** @type {Array<string|Node>} */ (items);
|
|
88
|
-
this.menus = Array.from(this.form.querySelectorAll('li'));
|
|
89
|
-
this.menuLen = this.menus.length;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @description Initializes the select menu and attaches it to a reference element.
|
|
94
|
-
* @param {Node} referElement - The element that triggers the select menu.
|
|
95
|
-
* @param {(command: string) => void} selectMethod - The function to execute when an item is selected.
|
|
96
|
-
* @param {{class?: string, style?: string}} [attr={}] - Additional attributes for the select menu container.
|
|
97
|
-
*/
|
|
98
|
-
on(referElement, selectMethod, attr) {
|
|
99
|
-
if (!attr) attr = {};
|
|
100
|
-
this._refer = /** @type {HTMLElement} */ (referElement);
|
|
101
|
-
this._keydownTarget = dom.check.isInputElement(referElement) ? referElement : this.
|
|
102
|
-
this._selectMethod = selectMethod;
|
|
103
|
-
this.form = dom.utils.createElement(
|
|
104
|
-
'DIV',
|
|
105
|
-
{
|
|
106
|
-
class: 'se-select-menu' + (attr.class ? ' ' + attr.class : ''),
|
|
107
|
-
style: attr.style || ''
|
|
108
|
-
},
|
|
109
|
-
'<div class="se-list-inner"></div>'
|
|
110
|
-
);
|
|
111
|
-
referElement.parentNode.insertBefore(this.form, referElement);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @description Select menu open
|
|
116
|
-
* @param {?string=} position "[left|right]-[middle|top|bottom] | [top|bottom]-[center|left|right]"
|
|
117
|
-
* @param {?string=} onItemQuerySelector The querySelector string of the menu to be activated
|
|
118
|
-
*/
|
|
119
|
-
open(position, onItemQuerySelector) {
|
|
120
|
-
this.editor.selectMenuOn = true;
|
|
121
|
-
if (typeof this.openMethod === 'function') this.openMethod();
|
|
122
|
-
this.__addEvents();
|
|
123
|
-
this.__addGlobalEvent();
|
|
124
|
-
const positionItems = position ? position.split('-') : [];
|
|
125
|
-
const mainPosition = positionItems[0] || (this._textDirDiff !== null && this._textDirDiff !== this.options.get('_rtl') ? this._dirPosition : this.position);
|
|
126
|
-
const subPosition = positionItems[1] || (this._textDirDiff !== null && this._textDirDiff !== this.options.get('_rtl') ? this._dirSubPosition : this.subPosition);
|
|
127
|
-
this._setPosition(mainPosition, subPosition, onItemQuerySelector);
|
|
128
|
-
this.isOpen = true;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* @description Select menu close
|
|
133
|
-
*/
|
|
134
|
-
close() {
|
|
135
|
-
this.editor.selectMenuOn = false;
|
|
136
|
-
dom.utils.removeClass(this._refer, 'on');
|
|
137
|
-
this._init();
|
|
138
|
-
if (this.form) this.form.style.cssText = '';
|
|
139
|
-
this.isOpen = false;
|
|
140
|
-
if (typeof this.closeMethod === 'function') this.closeMethod();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* @description Get the index of the selected item
|
|
145
|
-
* @param {number} index Item index
|
|
146
|
-
* @returns
|
|
147
|
-
*/
|
|
148
|
-
getItem(index) {
|
|
149
|
-
return this.items[index];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* @description Set the index of the selected item
|
|
154
|
-
* @param {number} index Item index
|
|
155
|
-
*/
|
|
156
|
-
setItem(index) {
|
|
157
|
-
this._selectItem(index);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* @private
|
|
162
|
-
* @description Appends a formatted list of items to the menu.
|
|
163
|
-
* @param {string} html - The HTML string representing the menu items.
|
|
164
|
-
*/
|
|
165
|
-
_createFormat(html) {
|
|
166
|
-
this.form.firstElementChild.innerHTML += `<ul class="se-list-basic se-list-checked${this.horizontal ? ' se-list-horizontal' : ''}">${html}</ul>`;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* @private
|
|
171
|
-
* @description Resets the menu state and removes event listeners.
|
|
172
|
-
*/
|
|
173
|
-
_init() {
|
|
174
|
-
this.__removeEvents();
|
|
175
|
-
this.__removeGlobalEvent();
|
|
176
|
-
this.index = -1;
|
|
177
|
-
this.item = null;
|
|
178
|
-
if (this._onItem) {
|
|
179
|
-
dom.utils.removeClass(this._onItem, 'se-select-on');
|
|
180
|
-
this._onItem = null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* @private
|
|
186
|
-
* @description Moves the selection up or down by a specified number of items.
|
|
187
|
-
* @param {number} num - The number of items to move (negative for up, positive for down).
|
|
188
|
-
*/
|
|
189
|
-
_moveItem(num) {
|
|
190
|
-
num = this.index + num;
|
|
191
|
-
const len = this.menuLen;
|
|
192
|
-
const selectIndex = (this.index = num >= len ? 0 : num < 0 ? len - 1 : num);
|
|
193
|
-
|
|
194
|
-
this._selectItem(selectIndex);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* @private
|
|
199
|
-
* @description Highlights and selects an item by index.
|
|
200
|
-
* @param {number} selectIndex - The index of the item to select.
|
|
201
|
-
*/
|
|
202
|
-
_selectItem(selectIndex) {
|
|
203
|
-
dom.utils.removeClass(this.form, 'se-select-menu-mouse-move');
|
|
204
|
-
|
|
205
|
-
const len = this.menuLen;
|
|
206
|
-
for (let i = 0; i < len; i++) {
|
|
207
|
-
if (i === selectIndex) {
|
|
208
|
-
dom.utils.addClass(this.menus[i], 'active');
|
|
209
|
-
} else {
|
|
210
|
-
dom.utils.removeClass(this.menus[i], 'active');
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
this.index = selectIndex;
|
|
215
|
-
this.item = this.items[selectIndex];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* @private
|
|
220
|
-
* @description Sets the position of the select menu relative to the reference element.
|
|
221
|
-
* @param {string} position Menu position ("left"|"right") | ("top"|"bottom")
|
|
222
|
-
* @param {string} subPosition Sub position ("middle"|"top"|"bottom") | ("center"|"left"|"right")
|
|
223
|
-
* @param {string} [onItemQuerySelector] - A query selector string to highlight a specific item.
|
|
224
|
-
* @param {boolean} [_re=false] - Whether this is a retry after adjusting the position.
|
|
225
|
-
*/
|
|
226
|
-
_setPosition(position, subPosition, onItemQuerySelector, _re) {
|
|
227
|
-
const originP = position;
|
|
228
|
-
const form = this.form;
|
|
229
|
-
const target = this._refer;
|
|
230
|
-
form.style.visibility = 'hidden';
|
|
231
|
-
form.style.display = 'block';
|
|
232
|
-
dom.utils.removeClass(form, 'se-select-menu-scroll');
|
|
233
|
-
dom.utils.addClass(target, 'on');
|
|
234
|
-
|
|
235
|
-
const formW = form.offsetWidth;
|
|
236
|
-
const targetW = target.offsetWidth;
|
|
237
|
-
const targetL = target.offsetLeft;
|
|
238
|
-
let side = false;
|
|
239
|
-
let l = 0,
|
|
240
|
-
t = 0;
|
|
241
|
-
|
|
242
|
-
if (position === 'left') {
|
|
243
|
-
l = targetL - formW - 1;
|
|
244
|
-
position = subPosition;
|
|
245
|
-
side = true;
|
|
246
|
-
} else if (position === 'right') {
|
|
247
|
-
l = targetL + targetW + 1;
|
|
248
|
-
position = subPosition;
|
|
249
|
-
side = true;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// set top position
|
|
253
|
-
const globalTarget = this.editor.offset.get(target);
|
|
254
|
-
const targetOffsetTop = target.offsetTop;
|
|
255
|
-
const targetGlobalTop = globalTarget.top;
|
|
256
|
-
const targetHeight = target.offsetHeight;
|
|
257
|
-
const wbottom = dom.utils.getClientSize().h - (targetGlobalTop - this._w.scrollY + targetHeight);
|
|
258
|
-
const sideAddH = side ? targetHeight : 0;
|
|
259
|
-
let overH = 10000;
|
|
260
|
-
switch (position) {
|
|
261
|
-
case 'middle': {
|
|
262
|
-
let h = form.offsetHeight;
|
|
263
|
-
const th = targetHeight / 2;
|
|
264
|
-
t = targetOffsetTop - h / 2 + th;
|
|
265
|
-
// over top
|
|
266
|
-
if (targetGlobalTop < h / 2) {
|
|
267
|
-
t += h / 2 - targetGlobalTop - th + 4;
|
|
268
|
-
form.style.top = t + 'px';
|
|
269
|
-
}
|
|
270
|
-
// over bottom
|
|
271
|
-
let formT = this.editor.offset.getGlobal(form).top;
|
|
272
|
-
const modH = h - (targetGlobalTop - formT) - wbottom - targetHeight;
|
|
273
|
-
if (modH > 0) {
|
|
274
|
-
t -= modH + 4;
|
|
275
|
-
form.style.top = t + 'px';
|
|
276
|
-
}
|
|
277
|
-
// over height
|
|
278
|
-
formT = this.editor.offset.getGlobal(form).top;
|
|
279
|
-
if (formT < 0) {
|
|
280
|
-
h += formT - 4;
|
|
281
|
-
t -= formT - 4;
|
|
282
|
-
}
|
|
283
|
-
form.style.height = h + 'px';
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
case 'top':
|
|
287
|
-
if (targetGlobalTop < form.offsetHeight - sideAddH) {
|
|
288
|
-
if (!_re) {
|
|
289
|
-
overH = 0;
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
overH = targetGlobalTop - 4 + sideAddH;
|
|
293
|
-
if (overH >= MENU_MIN_HEIGHT) form.style.height = overH + 'px';
|
|
294
|
-
}
|
|
295
|
-
t = targetOffsetTop - form.offsetHeight + sideAddH;
|
|
296
|
-
break;
|
|
297
|
-
case 'bottom':
|
|
298
|
-
if (wbottom < form.offsetHeight + sideAddH) {
|
|
299
|
-
if (!_re) {
|
|
300
|
-
overH = 0;
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
overH = wbottom - 4 + sideAddH;
|
|
304
|
-
if (overH >= MENU_MIN_HEIGHT) form.style.height = overH + 'px';
|
|
305
|
-
}
|
|
306
|
-
t = targetOffsetTop + (side ? 0 : targetHeight);
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (overH < MENU_MIN_HEIGHT && !_re && position !== 'middle') {
|
|
311
|
-
this._setPosition(position === 'top' ? 'bottpm' : 'top', subPosition, onItemQuerySelector, true);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (!side) {
|
|
316
|
-
switch (subPosition) {
|
|
317
|
-
case 'center':
|
|
318
|
-
l = targetL + targetW / 2 - formW / 2;
|
|
319
|
-
break;
|
|
320
|
-
case 'left':
|
|
321
|
-
l = targetL;
|
|
322
|
-
break;
|
|
323
|
-
case 'right':
|
|
324
|
-
l = targetL - (formW - targetW);
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
form.style.left = l + 'px';
|
|
330
|
-
const fl = this.editor.offset.getGlobal(form).left;
|
|
331
|
-
let overW = 0;
|
|
332
|
-
switch (side + '-' + (side ? originP : subPosition)) {
|
|
333
|
-
case 'true-left':
|
|
334
|
-
overW = globalTarget.left - this._w.scrollX + fl;
|
|
335
|
-
if (overW < 0) l = l = targetL + targetW + 1;
|
|
336
|
-
break;
|
|
337
|
-
case 'true-right':
|
|
338
|
-
overW = this._w.innerWidth - (fl + formW);
|
|
339
|
-
if (overW < 0) l = targetL - formW - 1;
|
|
340
|
-
break;
|
|
341
|
-
case 'false-center': {
|
|
342
|
-
overW = this._w.innerWidth - (fl + formW);
|
|
343
|
-
if (overW < 0) l += overW - 4;
|
|
344
|
-
form.style.left = l + 'px';
|
|
345
|
-
const centerfl = this.editor.offset.getGlobal(form).left;
|
|
346
|
-
if (centerfl < 0) l -= centerfl - 4;
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
|
-
case 'false-left':
|
|
350
|
-
overW = this._w.innerWidth - (globalTarget.left - this._w.scrollX + formW);
|
|
351
|
-
if (overW < 0) l += overW - 4;
|
|
352
|
-
break;
|
|
353
|
-
case 'false-right':
|
|
354
|
-
if (fl < 0) l -= fl - 4;
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (onItemQuerySelector) {
|
|
359
|
-
const item = form.firstElementChild.querySelector(onItemQuerySelector);
|
|
360
|
-
if (item) {
|
|
361
|
-
this._onItem = item;
|
|
362
|
-
dom.utils.addClass(item, 'se-select-on');
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
form.style.left = l + 'px';
|
|
367
|
-
form.style.top = t + 'px';
|
|
368
|
-
form.style.visibility = '';
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* @private
|
|
373
|
-
* @description Selects an item and triggers the callback function.
|
|
374
|
-
* @param {number} index - The index of the item to select.
|
|
375
|
-
*/
|
|
376
|
-
_select(index) {
|
|
377
|
-
if (this.checkList) dom.utils.toggleClass(this.menus[index], 'se-checked');
|
|
378
|
-
this._selectMethod(this.getItem(index));
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* @private
|
|
383
|
-
* @description Adds event listeners for menu interactions.
|
|
384
|
-
*/
|
|
385
|
-
__addEvents() {
|
|
386
|
-
this.__removeEvents();
|
|
387
|
-
this.__events = this.__eventHandlers;
|
|
388
|
-
this.form.addEventListener('mousedown', this.__events.mousedown);
|
|
389
|
-
this.form.addEventListener('mousemove', this.__events.mousemove);
|
|
390
|
-
this.form.addEventListener('click', this.__events.click);
|
|
391
|
-
this._keydownTarget.addEventListener('keydown', this.__events.keydown);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* @private
|
|
396
|
-
* @description Removes event listeners for menu interactions.
|
|
397
|
-
*/
|
|
398
|
-
__removeEvents() {
|
|
399
|
-
if (!this.__events) return;
|
|
400
|
-
this.form.removeEventListener('mousedown', this.__events.mousedown);
|
|
401
|
-
this.form.removeEventListener('mousemove', this.__events.mousemove);
|
|
402
|
-
this.form.removeEventListener('click', this.__events.click);
|
|
403
|
-
this._keydownTarget.removeEventListener('keydown', this.__events.keydown);
|
|
404
|
-
this.__events = null;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* @private
|
|
409
|
-
* @description Adds global event listeners for closing the menu.
|
|
410
|
-
*/
|
|
411
|
-
__addGlobalEvent() {
|
|
412
|
-
this.__removeGlobalEvent();
|
|
413
|
-
this._bindClose_key = this.eventManager.addGlobalEvent('keydown', this.__globalEventHandlers.keydown, true);
|
|
414
|
-
this._bindClose_mousedown = this.eventManager.addGlobalEvent('mousedown', this.__globalEventHandlers.mousedown, true);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* @private
|
|
419
|
-
* @description Removes global event listeners for closing the menu.
|
|
420
|
-
*/
|
|
421
|
-
__removeGlobalEvent() {
|
|
422
|
-
if (this._bindClose_key) this._bindClose_key = this.eventManager.removeGlobalEvent(this._bindClose_key);
|
|
423
|
-
if (this._bindClose_mousedown) this._bindClose_mousedown = this.eventManager.removeGlobalEvent(this._bindClose_mousedown);
|
|
424
|
-
if (this._bindClose_click) this._bindClose_click = this.eventManager.removeGlobalEvent(this._bindClose_click);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* @param {KeyboardEvent} e - Event object
|
|
429
|
-
*/
|
|
430
|
-
#OnKeyDown_refer(e) {
|
|
431
|
-
let moveIndex;
|
|
432
|
-
switch (e.code) {
|
|
433
|
-
case 'ArrowUp': // up
|
|
434
|
-
e.preventDefault();
|
|
435
|
-
e.stopPropagation();
|
|
436
|
-
if (this.horizontal && this.index > -1) {
|
|
437
|
-
const num = this.splitNum;
|
|
438
|
-
moveIndex = this.index - num < 0 ? num : -num;
|
|
439
|
-
} else {
|
|
440
|
-
moveIndex = -1;
|
|
441
|
-
}
|
|
442
|
-
break;
|
|
443
|
-
case 'ArrowDown': // down
|
|
444
|
-
e.preventDefault();
|
|
445
|
-
e.stopPropagation();
|
|
446
|
-
if (this.horizontal && this.index > -1) {
|
|
447
|
-
const num = this.splitNum;
|
|
448
|
-
moveIndex = this.index + num > this.menuLen ? -num : num;
|
|
449
|
-
} else {
|
|
450
|
-
moveIndex = 1;
|
|
451
|
-
}
|
|
452
|
-
break;
|
|
453
|
-
case 'ArrowLeft': // left
|
|
454
|
-
e.preventDefault();
|
|
455
|
-
e.stopPropagation();
|
|
456
|
-
moveIndex = -1;
|
|
457
|
-
break;
|
|
458
|
-
case 'ArrowRight': //right
|
|
459
|
-
e.preventDefault();
|
|
460
|
-
e.stopPropagation();
|
|
461
|
-
moveIndex = 1;
|
|
462
|
-
break;
|
|
463
|
-
case 'Enter':
|
|
464
|
-
case 'Space': // enter, space
|
|
465
|
-
if (this.index > -1) {
|
|
466
|
-
e.preventDefault();
|
|
467
|
-
e.stopPropagation();
|
|
468
|
-
this._select(this.index);
|
|
469
|
-
} else {
|
|
470
|
-
this.close();
|
|
471
|
-
}
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (moveIndex) this._moveItem(moveIndex);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* @param {MouseEvent} e - Event object
|
|
480
|
-
*/
|
|
481
|
-
#OnMousedown_list(e) {
|
|
482
|
-
if (env.isGecko) {
|
|
483
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
484
|
-
const target = dom.query.getParentElement(eventTarget, '.se-select-item');
|
|
485
|
-
if (target) this.eventManager._injectActiveEvent(target);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* @param {MouseEvent} e - Event object
|
|
491
|
-
*/
|
|
492
|
-
#OnMouseMove_list(e) {
|
|
493
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
494
|
-
dom.utils.addClass(this.form, 'se-select-menu-mouse-move');
|
|
495
|
-
const index = eventTarget.getAttribute('data-index');
|
|
496
|
-
if (!index) return;
|
|
497
|
-
this.index = Number(index);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* @param {MouseEvent} e - Event object
|
|
502
|
-
*/
|
|
503
|
-
#OnClick_list(e) {
|
|
504
|
-
let target = dom.query.getEventTarget(e);
|
|
505
|
-
let index = null;
|
|
506
|
-
|
|
507
|
-
while (!index && !/UL/i.test(target.tagName) && !dom.utils.hasClass(target, 'se-select-menu')) {
|
|
508
|
-
index = target.getAttribute('data-index');
|
|
509
|
-
target = target.parentElement;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
if (!index) return;
|
|
513
|
-
this._select(Number(index));
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* @param {KeyboardEvent} e - Event object
|
|
518
|
-
*/
|
|
519
|
-
#CloseListener_key(e) {
|
|
520
|
-
if (!keyCodeMap.isEsc(e.code)) return;
|
|
521
|
-
this.close();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* @param {MouseEvent} e - Event object
|
|
526
|
-
*/
|
|
527
|
-
#CloseListener_mousedown(e) {
|
|
528
|
-
const eventTarget = dom.query.getEventTarget(e);
|
|
529
|
-
if (this.form.contains(eventTarget)) return;
|
|
530
|
-
if (e.target !== this._refer) {
|
|
531
|
-
this.close();
|
|
532
|
-
} else if (!dom.check.isInputElement(eventTarget)) {
|
|
533
|
-
this._bindClose_click = this.eventManager.addGlobalEvent('click', this.__globalEventHandlers.click, true);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* @param {MouseEvent} e - Event object
|
|
539
|
-
*/
|
|
540
|
-
#CloseListener_click(e) {
|
|
541
|
-
this._bindClose_click = this.eventManager.removeGlobalEvent(this._bindClose_click);
|
|
542
|
-
if (e.target === this._refer) {
|
|
543
|
-
e.stopPropagation();
|
|
544
|
-
this.close();
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
export default SelectMenu;
|
|
1
|
+
import CoreInjector from '../editorInjector/_core';
|
|
2
|
+
import { dom, env, keyCodeMap } from '../helper';
|
|
3
|
+
|
|
4
|
+
const MENU_MIN_HEIGHT = 38;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} SelectMenuParams
|
|
8
|
+
* @property {string} position Position of the select menu, specified as "[left|right]-[middle|top|bottom]" or "[top|bottom]-[center|left|right]"
|
|
9
|
+
* @property {boolean} [checkList=false] Flag to determine if the checklist is enabled (true or false)
|
|
10
|
+
* @property {"rtl" | "ltr"} [dir="ltr"] Optional text direction: "rtl" for right-to-left, "ltr" for left-to-right
|
|
11
|
+
* @property {number} [splitNum=0] Optional split number for horizontal positioning; defines how many items per row
|
|
12
|
+
* @property {() => void=} openMethod Optional method to call when the menu is opened
|
|
13
|
+
* @property {() => void=} closeMethod Optional method to call when the menu is closed
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @class
|
|
18
|
+
* @description Creates a select menu
|
|
19
|
+
*/
|
|
20
|
+
class SelectMenu extends CoreInjector {
|
|
21
|
+
/**
|
|
22
|
+
* @constructor
|
|
23
|
+
* @param {*} inst The instance object that called the constructor.
|
|
24
|
+
* @param {SelectMenuParams} params Select menu options
|
|
25
|
+
*/
|
|
26
|
+
constructor(inst, params) {
|
|
27
|
+
// plugin bisic properties
|
|
28
|
+
super(inst.editor);
|
|
29
|
+
|
|
30
|
+
// members
|
|
31
|
+
this.kink = inst.constructor.key || inst.constructor.name;
|
|
32
|
+
this.inst = inst;
|
|
33
|
+
const positionItems = params.position.split('-');
|
|
34
|
+
this.form = null;
|
|
35
|
+
this.items = [];
|
|
36
|
+
/** @type {HTMLLIElement[]} */
|
|
37
|
+
this.menus = null;
|
|
38
|
+
this.menuLen = 0;
|
|
39
|
+
this.index = -1;
|
|
40
|
+
this.item = null;
|
|
41
|
+
this.isOpen = false;
|
|
42
|
+
this.checkList = !!params.checkList;
|
|
43
|
+
this.position = positionItems[0];
|
|
44
|
+
this.subPosition = positionItems[1];
|
|
45
|
+
this._dirPosition = /^(left|right)$/.test(this.position) ? (this.position === 'left' ? 'right' : 'left') : this.position;
|
|
46
|
+
this._dirSubPosition = /^(left|right)$/.test(this.subPosition) ? (this.subPosition === 'left' ? 'right' : 'left') : this.subPosition;
|
|
47
|
+
this._textDirDiff = params.dir === 'ltr' ? false : params.dir === 'rtl' ? true : null;
|
|
48
|
+
this.splitNum = params.splitNum || 0;
|
|
49
|
+
this.horizontal = !!this.splitNum;
|
|
50
|
+
this.openMethod = params.openMethod;
|
|
51
|
+
this.closeMethod = params.closeMethod;
|
|
52
|
+
this._refer = null;
|
|
53
|
+
this._keydownTarget = null;
|
|
54
|
+
this._selectMethod = null;
|
|
55
|
+
this._bindClose_key = null;
|
|
56
|
+
this._bindClose_mousedown = null;
|
|
57
|
+
this._bindClose_click = null;
|
|
58
|
+
this._closeSignal = false;
|
|
59
|
+
this.__events = null;
|
|
60
|
+
this.__eventHandlers = {
|
|
61
|
+
mousedown: this.#OnMousedown_list.bind(this),
|
|
62
|
+
mousemove: this.#OnMouseMove_list.bind(this),
|
|
63
|
+
click: this.#OnClick_list.bind(this),
|
|
64
|
+
keydown: this.#OnKeyDown_refer.bind(this)
|
|
65
|
+
};
|
|
66
|
+
this.__globalEventHandlers = { keydown: this.#CloseListener_key.bind(this), mousedown: this.#CloseListener_mousedown.bind(this), click: this.#CloseListener_click.bind(this) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @description Creates the select menu items.
|
|
71
|
+
* @param {Array<string>|__se__NodeCollection} items - Command list of selectable items.
|
|
72
|
+
* @param {Array<string>|__se__NodeCollection} [menus] - Optional list of menu display elements; defaults to `items`.
|
|
73
|
+
*/
|
|
74
|
+
create(items, menus) {
|
|
75
|
+
this.form.firstElementChild.innerHTML = '';
|
|
76
|
+
menus = menus || items;
|
|
77
|
+
let html = '';
|
|
78
|
+
for (let i = 0, len = menus.length; i < len; i++) {
|
|
79
|
+
if (i > 0 && i % this.splitNum === 0) {
|
|
80
|
+
this._createFormat(html);
|
|
81
|
+
html = '';
|
|
82
|
+
}
|
|
83
|
+
html += `<li class="se-select-item" data-index="${i}">${typeof menus[i] === 'string' ? menus[i] : /** @type {HTMLElement} */ (menus[i]).outerHTML}</li>`;
|
|
84
|
+
}
|
|
85
|
+
this._createFormat(html);
|
|
86
|
+
|
|
87
|
+
this.items = /** @type {Array<string|Node>} */ (items);
|
|
88
|
+
this.menus = Array.from(this.form.querySelectorAll('li'));
|
|
89
|
+
this.menuLen = this.menus.length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @description Initializes the select menu and attaches it to a reference element.
|
|
94
|
+
* @param {Node} referElement - The element that triggers the select menu.
|
|
95
|
+
* @param {(command: string) => void} selectMethod - The function to execute when an item is selected.
|
|
96
|
+
* @param {{class?: string, style?: string}} [attr={}] - Additional attributes for the select menu container.
|
|
97
|
+
*/
|
|
98
|
+
on(referElement, selectMethod, attr) {
|
|
99
|
+
if (!attr) attr = {};
|
|
100
|
+
this._refer = /** @type {HTMLElement} */ (referElement);
|
|
101
|
+
this._keydownTarget = dom.check.isInputElement(referElement) ? referElement : this.editor.frameContext.get('_ww');
|
|
102
|
+
this._selectMethod = selectMethod;
|
|
103
|
+
this.form = dom.utils.createElement(
|
|
104
|
+
'DIV',
|
|
105
|
+
{
|
|
106
|
+
class: 'se-select-menu' + (attr.class ? ' ' + attr.class : ''),
|
|
107
|
+
style: attr.style || ''
|
|
108
|
+
},
|
|
109
|
+
'<div class="se-list-inner"></div>'
|
|
110
|
+
);
|
|
111
|
+
referElement.parentNode.insertBefore(this.form, referElement);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @description Select menu open
|
|
116
|
+
* @param {?string=} position "[left|right]-[middle|top|bottom] | [top|bottom]-[center|left|right]"
|
|
117
|
+
* @param {?string=} onItemQuerySelector The querySelector string of the menu to be activated
|
|
118
|
+
*/
|
|
119
|
+
open(position, onItemQuerySelector) {
|
|
120
|
+
this.editor.selectMenuOn = true;
|
|
121
|
+
if (typeof this.openMethod === 'function') this.openMethod();
|
|
122
|
+
this.__addEvents();
|
|
123
|
+
this.__addGlobalEvent();
|
|
124
|
+
const positionItems = position ? position.split('-') : [];
|
|
125
|
+
const mainPosition = positionItems[0] || (this._textDirDiff !== null && this._textDirDiff !== this.options.get('_rtl') ? this._dirPosition : this.position);
|
|
126
|
+
const subPosition = positionItems[1] || (this._textDirDiff !== null && this._textDirDiff !== this.options.get('_rtl') ? this._dirSubPosition : this.subPosition);
|
|
127
|
+
this._setPosition(mainPosition, subPosition, onItemQuerySelector);
|
|
128
|
+
this.isOpen = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @description Select menu close
|
|
133
|
+
*/
|
|
134
|
+
close() {
|
|
135
|
+
this.editor.selectMenuOn = false;
|
|
136
|
+
dom.utils.removeClass(this._refer, 'on');
|
|
137
|
+
this._init();
|
|
138
|
+
if (this.form) this.form.style.cssText = '';
|
|
139
|
+
this.isOpen = false;
|
|
140
|
+
if (typeof this.closeMethod === 'function') this.closeMethod();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @description Get the index of the selected item
|
|
145
|
+
* @param {number} index Item index
|
|
146
|
+
* @returns
|
|
147
|
+
*/
|
|
148
|
+
getItem(index) {
|
|
149
|
+
return this.items[index];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @description Set the index of the selected item
|
|
154
|
+
* @param {number} index Item index
|
|
155
|
+
*/
|
|
156
|
+
setItem(index) {
|
|
157
|
+
this._selectItem(index);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @private
|
|
162
|
+
* @description Appends a formatted list of items to the menu.
|
|
163
|
+
* @param {string} html - The HTML string representing the menu items.
|
|
164
|
+
*/
|
|
165
|
+
_createFormat(html) {
|
|
166
|
+
this.form.firstElementChild.innerHTML += `<ul class="se-list-basic se-list-checked${this.horizontal ? ' se-list-horizontal' : ''}">${html}</ul>`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @private
|
|
171
|
+
* @description Resets the menu state and removes event listeners.
|
|
172
|
+
*/
|
|
173
|
+
_init() {
|
|
174
|
+
this.__removeEvents();
|
|
175
|
+
this.__removeGlobalEvent();
|
|
176
|
+
this.index = -1;
|
|
177
|
+
this.item = null;
|
|
178
|
+
if (this._onItem) {
|
|
179
|
+
dom.utils.removeClass(this._onItem, 'se-select-on');
|
|
180
|
+
this._onItem = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @private
|
|
186
|
+
* @description Moves the selection up or down by a specified number of items.
|
|
187
|
+
* @param {number} num - The number of items to move (negative for up, positive for down).
|
|
188
|
+
*/
|
|
189
|
+
_moveItem(num) {
|
|
190
|
+
num = this.index + num;
|
|
191
|
+
const len = this.menuLen;
|
|
192
|
+
const selectIndex = (this.index = num >= len ? 0 : num < 0 ? len - 1 : num);
|
|
193
|
+
|
|
194
|
+
this._selectItem(selectIndex);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @private
|
|
199
|
+
* @description Highlights and selects an item by index.
|
|
200
|
+
* @param {number} selectIndex - The index of the item to select.
|
|
201
|
+
*/
|
|
202
|
+
_selectItem(selectIndex) {
|
|
203
|
+
dom.utils.removeClass(this.form, 'se-select-menu-mouse-move');
|
|
204
|
+
|
|
205
|
+
const len = this.menuLen;
|
|
206
|
+
for (let i = 0; i < len; i++) {
|
|
207
|
+
if (i === selectIndex) {
|
|
208
|
+
dom.utils.addClass(this.menus[i], 'active');
|
|
209
|
+
} else {
|
|
210
|
+
dom.utils.removeClass(this.menus[i], 'active');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this.index = selectIndex;
|
|
215
|
+
this.item = this.items[selectIndex];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @private
|
|
220
|
+
* @description Sets the position of the select menu relative to the reference element.
|
|
221
|
+
* @param {string} position Menu position ("left"|"right") | ("top"|"bottom")
|
|
222
|
+
* @param {string} subPosition Sub position ("middle"|"top"|"bottom") | ("center"|"left"|"right")
|
|
223
|
+
* @param {string} [onItemQuerySelector] - A query selector string to highlight a specific item.
|
|
224
|
+
* @param {boolean} [_re=false] - Whether this is a retry after adjusting the position.
|
|
225
|
+
*/
|
|
226
|
+
_setPosition(position, subPosition, onItemQuerySelector, _re) {
|
|
227
|
+
const originP = position;
|
|
228
|
+
const form = this.form;
|
|
229
|
+
const target = this._refer;
|
|
230
|
+
form.style.visibility = 'hidden';
|
|
231
|
+
form.style.display = 'block';
|
|
232
|
+
dom.utils.removeClass(form, 'se-select-menu-scroll');
|
|
233
|
+
dom.utils.addClass(target, 'on');
|
|
234
|
+
|
|
235
|
+
const formW = form.offsetWidth;
|
|
236
|
+
const targetW = target.offsetWidth;
|
|
237
|
+
const targetL = target.offsetLeft;
|
|
238
|
+
let side = false;
|
|
239
|
+
let l = 0,
|
|
240
|
+
t = 0;
|
|
241
|
+
|
|
242
|
+
if (position === 'left') {
|
|
243
|
+
l = targetL - formW - 1;
|
|
244
|
+
position = subPosition;
|
|
245
|
+
side = true;
|
|
246
|
+
} else if (position === 'right') {
|
|
247
|
+
l = targetL + targetW + 1;
|
|
248
|
+
position = subPosition;
|
|
249
|
+
side = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// set top position
|
|
253
|
+
const globalTarget = this.editor.offset.get(target);
|
|
254
|
+
const targetOffsetTop = target.offsetTop;
|
|
255
|
+
const targetGlobalTop = globalTarget.top;
|
|
256
|
+
const targetHeight = target.offsetHeight;
|
|
257
|
+
const wbottom = dom.utils.getClientSize().h - (targetGlobalTop - this._w.scrollY + targetHeight);
|
|
258
|
+
const sideAddH = side ? targetHeight : 0;
|
|
259
|
+
let overH = 10000;
|
|
260
|
+
switch (position) {
|
|
261
|
+
case 'middle': {
|
|
262
|
+
let h = form.offsetHeight;
|
|
263
|
+
const th = targetHeight / 2;
|
|
264
|
+
t = targetOffsetTop - h / 2 + th;
|
|
265
|
+
// over top
|
|
266
|
+
if (targetGlobalTop < h / 2) {
|
|
267
|
+
t += h / 2 - targetGlobalTop - th + 4;
|
|
268
|
+
form.style.top = t + 'px';
|
|
269
|
+
}
|
|
270
|
+
// over bottom
|
|
271
|
+
let formT = this.editor.offset.getGlobal(form).top;
|
|
272
|
+
const modH = h - (targetGlobalTop - formT) - wbottom - targetHeight;
|
|
273
|
+
if (modH > 0) {
|
|
274
|
+
t -= modH + 4;
|
|
275
|
+
form.style.top = t + 'px';
|
|
276
|
+
}
|
|
277
|
+
// over height
|
|
278
|
+
formT = this.editor.offset.getGlobal(form).top;
|
|
279
|
+
if (formT < 0) {
|
|
280
|
+
h += formT - 4;
|
|
281
|
+
t -= formT - 4;
|
|
282
|
+
}
|
|
283
|
+
form.style.height = h + 'px';
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case 'top':
|
|
287
|
+
if (targetGlobalTop < form.offsetHeight - sideAddH) {
|
|
288
|
+
if (!_re) {
|
|
289
|
+
overH = 0;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
overH = targetGlobalTop - 4 + sideAddH;
|
|
293
|
+
if (overH >= MENU_MIN_HEIGHT) form.style.height = overH + 'px';
|
|
294
|
+
}
|
|
295
|
+
t = targetOffsetTop - form.offsetHeight + sideAddH;
|
|
296
|
+
break;
|
|
297
|
+
case 'bottom':
|
|
298
|
+
if (wbottom < form.offsetHeight + sideAddH) {
|
|
299
|
+
if (!_re) {
|
|
300
|
+
overH = 0;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
overH = wbottom - 4 + sideAddH;
|
|
304
|
+
if (overH >= MENU_MIN_HEIGHT) form.style.height = overH + 'px';
|
|
305
|
+
}
|
|
306
|
+
t = targetOffsetTop + (side ? 0 : targetHeight);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (overH < MENU_MIN_HEIGHT && !_re && position !== 'middle') {
|
|
311
|
+
this._setPosition(position === 'top' ? 'bottpm' : 'top', subPosition, onItemQuerySelector, true);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!side) {
|
|
316
|
+
switch (subPosition) {
|
|
317
|
+
case 'center':
|
|
318
|
+
l = targetL + targetW / 2 - formW / 2;
|
|
319
|
+
break;
|
|
320
|
+
case 'left':
|
|
321
|
+
l = targetL;
|
|
322
|
+
break;
|
|
323
|
+
case 'right':
|
|
324
|
+
l = targetL - (formW - targetW);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
form.style.left = l + 'px';
|
|
330
|
+
const fl = this.editor.offset.getGlobal(form).left;
|
|
331
|
+
let overW = 0;
|
|
332
|
+
switch (side + '-' + (side ? originP : subPosition)) {
|
|
333
|
+
case 'true-left':
|
|
334
|
+
overW = globalTarget.left - this._w.scrollX + fl;
|
|
335
|
+
if (overW < 0) l = l = targetL + targetW + 1;
|
|
336
|
+
break;
|
|
337
|
+
case 'true-right':
|
|
338
|
+
overW = this._w.innerWidth - (fl + formW);
|
|
339
|
+
if (overW < 0) l = targetL - formW - 1;
|
|
340
|
+
break;
|
|
341
|
+
case 'false-center': {
|
|
342
|
+
overW = this._w.innerWidth - (fl + formW);
|
|
343
|
+
if (overW < 0) l += overW - 4;
|
|
344
|
+
form.style.left = l + 'px';
|
|
345
|
+
const centerfl = this.editor.offset.getGlobal(form).left;
|
|
346
|
+
if (centerfl < 0) l -= centerfl - 4;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case 'false-left':
|
|
350
|
+
overW = this._w.innerWidth - (globalTarget.left - this._w.scrollX + formW);
|
|
351
|
+
if (overW < 0) l += overW - 4;
|
|
352
|
+
break;
|
|
353
|
+
case 'false-right':
|
|
354
|
+
if (fl < 0) l -= fl - 4;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (onItemQuerySelector) {
|
|
359
|
+
const item = form.firstElementChild.querySelector(onItemQuerySelector);
|
|
360
|
+
if (item) {
|
|
361
|
+
this._onItem = item;
|
|
362
|
+
dom.utils.addClass(item, 'se-select-on');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
form.style.left = l + 'px';
|
|
367
|
+
form.style.top = t + 'px';
|
|
368
|
+
form.style.visibility = '';
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @private
|
|
373
|
+
* @description Selects an item and triggers the callback function.
|
|
374
|
+
* @param {number} index - The index of the item to select.
|
|
375
|
+
*/
|
|
376
|
+
_select(index) {
|
|
377
|
+
if (this.checkList) dom.utils.toggleClass(this.menus[index], 'se-checked');
|
|
378
|
+
this._selectMethod(this.getItem(index));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* @private
|
|
383
|
+
* @description Adds event listeners for menu interactions.
|
|
384
|
+
*/
|
|
385
|
+
__addEvents() {
|
|
386
|
+
this.__removeEvents();
|
|
387
|
+
this.__events = this.__eventHandlers;
|
|
388
|
+
this.form.addEventListener('mousedown', this.__events.mousedown);
|
|
389
|
+
this.form.addEventListener('mousemove', this.__events.mousemove);
|
|
390
|
+
this.form.addEventListener('click', this.__events.click);
|
|
391
|
+
this._keydownTarget.addEventListener('keydown', this.__events.keydown);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @private
|
|
396
|
+
* @description Removes event listeners for menu interactions.
|
|
397
|
+
*/
|
|
398
|
+
__removeEvents() {
|
|
399
|
+
if (!this.__events) return;
|
|
400
|
+
this.form.removeEventListener('mousedown', this.__events.mousedown);
|
|
401
|
+
this.form.removeEventListener('mousemove', this.__events.mousemove);
|
|
402
|
+
this.form.removeEventListener('click', this.__events.click);
|
|
403
|
+
this._keydownTarget.removeEventListener('keydown', this.__events.keydown);
|
|
404
|
+
this.__events = null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* @private
|
|
409
|
+
* @description Adds global event listeners for closing the menu.
|
|
410
|
+
*/
|
|
411
|
+
__addGlobalEvent() {
|
|
412
|
+
this.__removeGlobalEvent();
|
|
413
|
+
this._bindClose_key = this.eventManager.addGlobalEvent('keydown', this.__globalEventHandlers.keydown, true);
|
|
414
|
+
this._bindClose_mousedown = this.eventManager.addGlobalEvent('mousedown', this.__globalEventHandlers.mousedown, true);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @private
|
|
419
|
+
* @description Removes global event listeners for closing the menu.
|
|
420
|
+
*/
|
|
421
|
+
__removeGlobalEvent() {
|
|
422
|
+
if (this._bindClose_key) this._bindClose_key = this.eventManager.removeGlobalEvent(this._bindClose_key);
|
|
423
|
+
if (this._bindClose_mousedown) this._bindClose_mousedown = this.eventManager.removeGlobalEvent(this._bindClose_mousedown);
|
|
424
|
+
if (this._bindClose_click) this._bindClose_click = this.eventManager.removeGlobalEvent(this._bindClose_click);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* @param {KeyboardEvent} e - Event object
|
|
429
|
+
*/
|
|
430
|
+
#OnKeyDown_refer(e) {
|
|
431
|
+
let moveIndex;
|
|
432
|
+
switch (e.code) {
|
|
433
|
+
case 'ArrowUp': // up
|
|
434
|
+
e.preventDefault();
|
|
435
|
+
e.stopPropagation();
|
|
436
|
+
if (this.horizontal && this.index > -1) {
|
|
437
|
+
const num = this.splitNum;
|
|
438
|
+
moveIndex = this.index - num < 0 ? num : -num;
|
|
439
|
+
} else {
|
|
440
|
+
moveIndex = -1;
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
case 'ArrowDown': // down
|
|
444
|
+
e.preventDefault();
|
|
445
|
+
e.stopPropagation();
|
|
446
|
+
if (this.horizontal && this.index > -1) {
|
|
447
|
+
const num = this.splitNum;
|
|
448
|
+
moveIndex = this.index + num > this.menuLen ? -num : num;
|
|
449
|
+
} else {
|
|
450
|
+
moveIndex = 1;
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
case 'ArrowLeft': // left
|
|
454
|
+
e.preventDefault();
|
|
455
|
+
e.stopPropagation();
|
|
456
|
+
moveIndex = -1;
|
|
457
|
+
break;
|
|
458
|
+
case 'ArrowRight': //right
|
|
459
|
+
e.preventDefault();
|
|
460
|
+
e.stopPropagation();
|
|
461
|
+
moveIndex = 1;
|
|
462
|
+
break;
|
|
463
|
+
case 'Enter':
|
|
464
|
+
case 'Space': // enter, space
|
|
465
|
+
if (this.index > -1) {
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
e.stopPropagation();
|
|
468
|
+
this._select(this.index);
|
|
469
|
+
} else {
|
|
470
|
+
this.close();
|
|
471
|
+
}
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (moveIndex) this._moveItem(moveIndex);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* @param {MouseEvent} e - Event object
|
|
480
|
+
*/
|
|
481
|
+
#OnMousedown_list(e) {
|
|
482
|
+
if (env.isGecko) {
|
|
483
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
484
|
+
const target = dom.query.getParentElement(eventTarget, '.se-select-item');
|
|
485
|
+
if (target) this.eventManager._injectActiveEvent(target);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* @param {MouseEvent} e - Event object
|
|
491
|
+
*/
|
|
492
|
+
#OnMouseMove_list(e) {
|
|
493
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
494
|
+
dom.utils.addClass(this.form, 'se-select-menu-mouse-move');
|
|
495
|
+
const index = eventTarget.getAttribute('data-index');
|
|
496
|
+
if (!index) return;
|
|
497
|
+
this.index = Number(index);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* @param {MouseEvent} e - Event object
|
|
502
|
+
*/
|
|
503
|
+
#OnClick_list(e) {
|
|
504
|
+
let target = dom.query.getEventTarget(e);
|
|
505
|
+
let index = null;
|
|
506
|
+
|
|
507
|
+
while (!index && !/UL/i.test(target.tagName) && !dom.utils.hasClass(target, 'se-select-menu')) {
|
|
508
|
+
index = target.getAttribute('data-index');
|
|
509
|
+
target = target.parentElement;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!index) return;
|
|
513
|
+
this._select(Number(index));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* @param {KeyboardEvent} e - Event object
|
|
518
|
+
*/
|
|
519
|
+
#CloseListener_key(e) {
|
|
520
|
+
if (!keyCodeMap.isEsc(e.code)) return;
|
|
521
|
+
this.close();
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* @param {MouseEvent} e - Event object
|
|
526
|
+
*/
|
|
527
|
+
#CloseListener_mousedown(e) {
|
|
528
|
+
const eventTarget = dom.query.getEventTarget(e);
|
|
529
|
+
if (this.form.contains(eventTarget)) return;
|
|
530
|
+
if (e.target !== this._refer) {
|
|
531
|
+
this.close();
|
|
532
|
+
} else if (!dom.check.isInputElement(eventTarget)) {
|
|
533
|
+
this._bindClose_click = this.eventManager.addGlobalEvent('click', this.__globalEventHandlers.click, true);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* @param {MouseEvent} e - Event object
|
|
539
|
+
*/
|
|
540
|
+
#CloseListener_click(e) {
|
|
541
|
+
this._bindClose_click = this.eventManager.removeGlobalEvent(this._bindClose_click);
|
|
542
|
+
if (e.target === this._refer) {
|
|
543
|
+
e.stopPropagation();
|
|
544
|
+
this.close();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export default SelectMenu;
|