suneditor 3.1.3 → 3.2.0
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/dist/suneditor-contents.min.css +1 -1
- package/dist/suneditor.min.css +28 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +2 -1
- package/src/assets/design/color.css +12 -0
- package/src/assets/design/size.css +9 -2
- package/src/assets/icons/defaultIcons.js +96 -47
- package/src/assets/suneditor-contents.css +3 -2
- package/src/assets/suneditor.css +480 -31
- package/src/core/config/contextProvider.js +2 -1
- package/src/core/config/eventManager.js +41 -2
- package/src/core/config/optionProvider.js +23 -2
- package/src/core/editor.js +32 -14
- package/src/core/event/actions/index.js +76 -9
- package/src/core/event/effects/common.registry.js +2 -1
- package/src/core/event/effects/keydown.registry.js +169 -33
- package/src/core/event/effects/ruleHelpers.js +20 -6
- package/src/core/event/eventOrchestrator.js +136 -23
- package/src/core/event/handlers/handler_toolbar.js +4 -2
- package/src/core/event/handlers/handler_ww_dragDrop.js +10 -0
- package/src/core/event/handlers/handler_ww_input.js +9 -2
- package/src/core/event/handlers/handler_ww_key.js +86 -17
- package/src/core/event/handlers/handler_ww_mouse.js +45 -8
- package/src/core/event/ports.js +23 -5
- package/src/core/event/reducers/keydown.reducer.js +15 -2
- package/src/core/event/rules/keydown.rule.arrow.js +12 -2
- package/src/core/event/rules/keydown.rule.backspace.js +110 -15
- package/src/core/event/rules/keydown.rule.delete.js +62 -7
- package/src/core/event/rules/keydown.rule.enter.js +62 -31
- package/src/core/event/support/defaultLineManager.js +9 -2
- package/src/core/event/support/selectionState.js +13 -3
- package/src/core/kernel/coreKernel.js +1 -0
- package/src/core/logic/dom/char.js +13 -3
- package/src/core/logic/dom/format.js +171 -39
- package/src/core/logic/dom/html.js +357 -87
- package/src/core/logic/dom/inline.js +229 -34
- package/src/core/logic/dom/listFormat.js +111 -20
- package/src/core/logic/dom/nodeTransform.js +36 -11
- package/src/core/logic/dom/offset.js +116 -31
- package/src/core/logic/dom/selection.js +147 -29
- package/src/core/logic/panel/blockHandle.js +785 -0
- package/src/core/logic/panel/blockResolver.js +278 -0
- package/src/core/logic/panel/finder.js +16 -6
- package/src/core/logic/panel/menu.js +119 -18
- package/src/core/logic/panel/toolbar.js +75 -19
- package/src/core/logic/panel/viewer.js +76 -20
- package/src/core/logic/shell/_commandExecutor.js +44 -9
- package/src/core/logic/shell/commandDispatcher.js +46 -4
- package/src/core/logic/shell/component.js +102 -25
- package/src/core/logic/shell/focusManager.js +13 -3
- package/src/core/logic/shell/history.js +21 -6
- package/src/core/logic/shell/pluginManager.js +21 -13
- package/src/core/logic/shell/shortcuts.js +9 -1
- package/src/core/logic/shell/ui.js +139 -37
- package/src/core/schema/frameContext.js +34 -3
- package/src/core/schema/options.js +61 -19
- package/src/core/section/constructor.js +537 -103
- package/src/core/section/documentType.js +35 -6
- package/src/helper/converter.js +24 -7
- package/src/helper/dom/domCheck.js +25 -5
- package/src/helper/dom/domQuery.js +2 -1
- package/src/helper/dom/domUtils.js +17 -4
- package/src/helper/env.js +11 -2
- package/src/helper/keyCodeMap.js +6 -2
- package/src/helper/markdown.js +24 -5
- package/src/helper/msOffice.js +9 -2
- package/src/interfaces/plugins.js +1 -1
- package/src/langs/ckb.js +0 -1
- package/src/langs/cs.js +0 -1
- package/src/langs/da.js +0 -1
- package/src/langs/de.js +0 -1
- package/src/langs/en.js +0 -1
- package/src/langs/es.js +0 -1
- package/src/langs/fa.js +0 -1
- package/src/langs/fr.js +0 -1
- package/src/langs/he.js +0 -1
- package/src/langs/hu.js +0 -1
- package/src/langs/index.js +27 -1
- package/src/langs/it.js +0 -1
- package/src/langs/ja.js +0 -1
- package/src/langs/km.js +0 -1
- package/src/langs/ko.js +0 -1
- package/src/langs/lv.js +0 -1
- package/src/langs/nl.js +0 -1
- package/src/langs/pl.js +0 -1
- package/src/langs/pt_br.js +0 -1
- package/src/langs/ro.js +0 -1
- package/src/langs/ru.js +0 -1
- package/src/langs/se.js +0 -1
- package/src/langs/tr.js +0 -1
- package/src/langs/uk.js +0 -1
- package/src/langs/ur.js +0 -1
- package/src/langs/zh_cn.js +0 -1
- package/src/modules/contract/Browser.js +68 -23
- package/src/modules/contract/ColorPicker.js +12 -4
- package/src/modules/contract/Controller.js +37 -7
- package/src/modules/contract/Figure.js +159 -36
- package/src/modules/contract/HueSlider.js +27 -4
- package/src/modules/contract/Modal.js +62 -11
- package/src/modules/manager/FileManager.js +50 -8
- package/src/modules/ui/CommandMenu.js +597 -0
- package/src/modules/ui/ModalAnchorEditor.js +41 -9
- package/src/modules/ui/SelectMenu.js +481 -36
- package/src/modules/ui/index.js +1 -0
- package/src/plugins/browser/audioGallery.js +8 -1
- package/src/plugins/browser/fileBrowser.js +15 -2
- package/src/plugins/browser/fileGallery.js +8 -1
- package/src/plugins/browser/imageGallery.js +24 -3
- package/src/plugins/browser/videoGallery.js +19 -4
- package/src/plugins/command/blockquote.js +6 -1
- package/src/plugins/command/codeBlock.js +43 -5
- package/src/plugins/command/exportPDF.js +20 -4
- package/src/plugins/command/fileUpload.js +46 -11
- package/src/plugins/command/list_bulleted.js +6 -1
- package/src/plugins/command/list_numbered.js +6 -1
- package/src/plugins/dropdown/align.js +21 -33
- package/src/plugins/dropdown/backgroundColor.js +10 -2
- package/src/plugins/dropdown/blockStyle.js +17 -22
- package/src/plugins/dropdown/font.js +34 -34
- package/src/plugins/dropdown/hr.js +15 -43
- package/src/plugins/dropdown/layout.js +10 -27
- package/src/plugins/dropdown/lineHeight.js +10 -33
- package/src/plugins/dropdown/list.js +19 -25
- package/src/plugins/dropdown/paragraphStyle.js +16 -24
- package/src/plugins/dropdown/table/index.js +72 -17
- package/src/plugins/dropdown/table/render/table.html.js +3 -1
- package/src/plugins/dropdown/table/services/table.cell.js +33 -7
- package/src/plugins/dropdown/table/services/table.clipboard.js +5 -1
- package/src/plugins/dropdown/table/services/table.grid.js +40 -9
- package/src/plugins/dropdown/table/services/table.resize.js +55 -11
- package/src/plugins/dropdown/table/services/table.selection.js +27 -7
- package/src/plugins/dropdown/table/services/table.style.js +120 -20
- package/src/plugins/dropdown/template.js +13 -29
- package/src/plugins/dropdown/textStyle.js +22 -35
- package/src/plugins/field/autocomplete.js +8 -4
- package/src/plugins/field/slashCommand.js +297 -0
- package/src/plugins/index.js +3 -0
- package/src/plugins/input/fontSize.js +37 -8
- package/src/plugins/input/pageNavigator.js +7 -1
- package/src/plugins/modal/audio.js +71 -17
- package/src/plugins/modal/drawing.js +41 -11
- package/src/plugins/modal/embed.js +132 -22
- package/src/plugins/modal/image/index.js +115 -28
- package/src/plugins/modal/image/services/image.size.js +9 -2
- package/src/plugins/modal/image/services/image.upload.js +38 -4
- package/src/plugins/modal/link.js +9 -2
- package/src/plugins/modal/math.js +10 -2
- package/src/plugins/modal/video/index.js +112 -26
- package/src/plugins/modal/video/render/video.html.js +4 -1
- package/src/plugins/modal/video/services/video.size.js +15 -4
- package/src/plugins/modal/video/services/video.upload.js +10 -2
- package/src/plugins/popup/anchor.js +14 -2
- package/src/suneditor.js +8 -2
- package/src/themes/cobalt.css +12 -0
- package/src/themes/cream.css +12 -0
- package/src/themes/dark.css +12 -0
- package/src/themes/midnight.css +12 -0
- package/src/typedef.js +32 -21
- package/types/assets/icons/defaultIcons.d.ts +1 -0
- package/types/core/config/contextProvider.d.ts +7 -1
- package/types/core/config/eventManager.d.ts +37 -4
- package/types/core/config/optionProvider.d.ts +5 -1
- package/types/core/event/actions/index.d.ts +44 -6
- package/types/core/event/effects/keydown.registry.d.ts +37 -7
- package/types/core/event/effects/ruleHelpers.d.ts +6 -1
- package/types/core/event/eventOrchestrator.d.ts +6 -1
- package/types/core/event/handlers/handler_ww_clipboard.d.ts +15 -3
- package/types/core/event/handlers/handler_ww_dragDrop.d.ts +14 -2
- package/types/core/event/handlers/handler_ww_input.d.ts +10 -2
- package/types/core/event/handlers/handler_ww_key.d.ts +10 -2
- package/types/core/event/handlers/handler_ww_mouse.d.ts +25 -5
- package/types/core/event/ports.d.ts +7 -2
- package/types/core/event/rules/keydown.rule.backspace.d.ts +6 -1
- package/types/core/kernel/store.d.ts +4 -1
- package/types/core/logic/dom/format.d.ts +32 -14
- package/types/core/logic/dom/html.d.ts +11 -3
- package/types/core/logic/dom/nodeTransform.d.ts +6 -6
- package/types/core/logic/panel/blockHandle.d.ts +64 -0
- package/types/core/logic/panel/blockResolver.d.ts +50 -0
- package/types/core/logic/panel/menu.d.ts +72 -3
- package/types/core/logic/shell/commandDispatcher.d.ts +8 -0
- package/types/core/logic/shell/component.d.ts +5 -3
- package/types/core/logic/shell/shortcuts.d.ts +10 -1
- package/types/core/logic/shell/ui.d.ts +7 -1
- package/types/core/schema/context.d.ts +7 -1
- package/types/core/schema/frameContext.d.ts +36 -4
- package/types/core/schema/options.d.ts +110 -37
- package/types/core/section/constructor.d.ts +38 -1
- package/types/events.d.ts +208 -32
- package/types/helper/converter.d.ts +4 -2
- package/types/helper/dom/domCheck.d.ts +12 -2
- package/types/helper/dom/domQuery.d.ts +35 -7
- package/types/helper/dom/domUtils.d.ts +4 -1
- package/types/interfaces/plugins.d.ts +2 -2
- package/types/langs/_Lang.d.ts +0 -1
- package/types/modules/contract/Browser.d.ts +7 -7
- package/types/modules/contract/ColorPicker.d.ts +2 -2
- package/types/modules/contract/Controller.d.ts +7 -0
- package/types/modules/contract/Figure.d.ts +31 -4
- package/types/modules/ui/CommandMenu.d.ts +262 -0
- package/types/modules/ui/ModalAnchorEditor.d.ts +6 -1
- package/types/modules/ui/SelectMenu.d.ts +56 -3
- package/types/modules/ui/index.d.ts +1 -0
- package/types/plugins/browser/audioGallery.d.ts +17 -1
- package/types/plugins/browser/fileBrowser.d.ts +17 -1
- package/types/plugins/browser/fileGallery.d.ts +17 -1
- package/types/plugins/browser/imageGallery.d.ts +17 -1
- package/types/plugins/browser/videoGallery.d.ts +17 -1
- package/types/plugins/dropdown/align.d.ts +1 -1
- package/types/plugins/dropdown/blockStyle.d.ts +3 -1
- package/types/plugins/dropdown/table/index.d.ts +4 -1
- package/types/plugins/dropdown/table/services/table.clipboard.d.ts +5 -1
- package/types/plugins/dropdown/table/services/table.grid.d.ts +10 -2
- package/types/plugins/field/autocomplete.d.ts +0 -1
- package/types/plugins/field/slashCommand.d.ts +165 -0
- package/types/plugins/index.d.ts +3 -0
- package/types/plugins/modal/embed.d.ts +34 -0
- package/types/plugins/modal/link.d.ts +4 -1
- package/types/typedef.d.ts +48 -23
|
@@ -16,8 +16,16 @@ const MENU_MIN_HEIGHT = 38;
|
|
|
16
16
|
* @property {number} [splitNum=0] Optional split number for horizontal positioning; defines how many items per row
|
|
17
17
|
* @property {() => void} [openMethod] Optional method to call when the menu is opened
|
|
18
18
|
* @property {() => void} [closeMethod] Optional method to call when the menu is closed
|
|
19
|
+
* @property {() => boolean} [subEscMethod] Optional owner hook invoked on ESC before the menu closes.
|
|
20
|
+
* Return `true` if it dismissed an owner-managed sub-panel (e.g. CommandMenu's flyout), so ESC only
|
|
21
|
+
* closes that sub-panel and keeps the menu open.
|
|
19
22
|
* @property {string} [maxHeight] Optional max-height CSS value (e.g. `"200px"`). Enables scrolling when items exceed this height.
|
|
20
23
|
* @property {string} [minWidth] Optional min-width CSS value (e.g. `"130px"`).
|
|
24
|
+
* @property {*} [keydownTarget] Optional override for the keyboard navigation target. By default `on()` listens
|
|
25
|
+
* - on the iframe `contentWindow` (`_ww`) when the refer isn't an input — appropriate when the
|
|
26
|
+
* - refer is inside the wysiwyg. Set this to `window` (parent) for menus whose refer lives in
|
|
27
|
+
* - the parent doc (e.g. BlockHandle's dragBtn in `carrierWrapper`). Also avoids
|
|
28
|
+
* - cross-origin/sandboxed iframe `addEventListener` errors.
|
|
21
29
|
*/
|
|
22
30
|
|
|
23
31
|
/**
|
|
@@ -35,11 +43,22 @@ class SelectMenu {
|
|
|
35
43
|
|
|
36
44
|
#refer = null;
|
|
37
45
|
#keydownTarget = null;
|
|
46
|
+
#keydownTargetOverride = null;
|
|
38
47
|
#selectMethod = null;
|
|
39
48
|
#bindClose_key = null;
|
|
40
49
|
#bindClose_mousedown = null;
|
|
41
50
|
#bindClose_click = null;
|
|
51
|
+
#bindSubmenuReposition = null;
|
|
42
52
|
#events = null;
|
|
53
|
+
#lastMainPosition = null;
|
|
54
|
+
#lastSubPosition = null;
|
|
55
|
+
|
|
56
|
+
// submenu
|
|
57
|
+
#submenuData = new Map();
|
|
58
|
+
#activeSubmenuIndex = -1;
|
|
59
|
+
#submenuItemIndex = -1;
|
|
60
|
+
#inSubmenu = false;
|
|
61
|
+
#submenuHoverTimer = null;
|
|
43
62
|
|
|
44
63
|
/**
|
|
45
64
|
* @constructor
|
|
@@ -66,11 +85,21 @@ class SelectMenu {
|
|
|
66
85
|
this.horizontal = !!this.splitNum;
|
|
67
86
|
this.openMethod = params.openMethod;
|
|
68
87
|
this.closeMethod = params.closeMethod;
|
|
88
|
+
this.subEscMethod = params.subEscMethod || null;
|
|
69
89
|
this.maxHeight = params.maxHeight || '';
|
|
70
90
|
this.minWidth = params.minWidth || '';
|
|
91
|
+
this.#keydownTargetOverride = params.keydownTarget || null;
|
|
71
92
|
|
|
72
|
-
this.#dirPosition = /^(left|right)$/.test(this.position)
|
|
73
|
-
|
|
93
|
+
this.#dirPosition = /^(left|right)$/.test(this.position)
|
|
94
|
+
? this.position === 'left'
|
|
95
|
+
? 'right'
|
|
96
|
+
: 'left'
|
|
97
|
+
: this.position;
|
|
98
|
+
this.#dirSubPosition = /^(left|right)$/.test(this.subPosition)
|
|
99
|
+
? this.subPosition === 'left'
|
|
100
|
+
? 'right'
|
|
101
|
+
: 'left'
|
|
102
|
+
: this.subPosition;
|
|
74
103
|
this.#textDirDiff = params.dir === 'ltr' ? false : params.dir === 'rtl' ? true : null;
|
|
75
104
|
|
|
76
105
|
this.#eventHandlers = {
|
|
@@ -79,16 +108,41 @@ class SelectMenu {
|
|
|
79
108
|
click: this.#OnClick_list.bind(this),
|
|
80
109
|
keydown: this.#OnKeyDown_refer.bind(this),
|
|
81
110
|
};
|
|
82
|
-
this.#globalEventHandlers = {
|
|
111
|
+
this.#globalEventHandlers = {
|
|
112
|
+
keydown: this.#CloseListener_key.bind(this),
|
|
113
|
+
mousedown: this.#CloseListener_mousedown.bind(this),
|
|
114
|
+
click: this.#CloseListener_click.bind(this),
|
|
115
|
+
submenuReposition: this.#OnSubmenuReposition.bind(this),
|
|
116
|
+
};
|
|
83
117
|
}
|
|
84
118
|
|
|
85
119
|
/**
|
|
86
120
|
* @description Creates the select menu items.
|
|
87
|
-
* @param {Array
|
|
88
|
-
*
|
|
121
|
+
* @param {Array<*>} items - Selectable items.
|
|
122
|
+
* - Plain entry: any value (string/object); passed to the `selectMethod` callback when picked.
|
|
123
|
+
* - Submenu entry: `{ children: Array<*>, childMenus?: Array<string|HTMLElement> }` —
|
|
124
|
+
* `children` are the child values delivered to `selectMethod` on selection; `childMenus`
|
|
125
|
+
* is the optional display content for each child (HTML string or `HTMLElement`). When
|
|
126
|
+
* omitted, `children` doubles as the display content.
|
|
127
|
+
* @param {Array<string>|SunEditor.NodeCollection} [menus] - Optional list of display elements
|
|
128
|
+
* (HTML strings or nodes) for the top-level rows. Defaults to `items`. For submenu entries
|
|
129
|
+
* this controls the parent row's content; child rows use `childMenus` (or `children`).
|
|
130
|
+
* @example
|
|
131
|
+
* // Submenu — "List" opens a hover submenu of UL/OL options
|
|
132
|
+
* selectMenu.create(
|
|
133
|
+
* [{ children: ['ul', 'ol'], childMenus: ['<i>•</i> Bulleted', '<i>1.</i> Numbered'] }],
|
|
134
|
+
* ['List']
|
|
135
|
+
* );
|
|
89
136
|
*/
|
|
90
137
|
create(items, menus) {
|
|
91
138
|
this.form.firstElementChild.innerHTML = '';
|
|
139
|
+
|
|
140
|
+
// remove existing submenu elements from form
|
|
141
|
+
for (const [, data] of this.#submenuData) {
|
|
142
|
+
data.element?.remove();
|
|
143
|
+
}
|
|
144
|
+
this.#submenuData.clear();
|
|
145
|
+
|
|
92
146
|
menus ||= items;
|
|
93
147
|
let html = '';
|
|
94
148
|
for (let i = 0, len = menus.length; i < len; i++) {
|
|
@@ -96,12 +150,46 @@ class SelectMenu {
|
|
|
96
150
|
this.#createFormat(html);
|
|
97
151
|
html = '';
|
|
98
152
|
}
|
|
99
|
-
|
|
153
|
+
|
|
154
|
+
const item = items[i];
|
|
155
|
+
const menuContent =
|
|
156
|
+
typeof menus[i] === 'string' ? menus[i] : /** @type {HTMLElement} */ (menus[i]).outerHTML;
|
|
157
|
+
const itemObj = /** @type {{children?: Array, childMenus?: Array}} */ (
|
|
158
|
+
item && typeof item === 'object' ? item : {}
|
|
159
|
+
);
|
|
160
|
+
const hasChildren = itemObj.children?.length > 0;
|
|
161
|
+
|
|
162
|
+
if (hasChildren) {
|
|
163
|
+
let subHtml = '';
|
|
164
|
+
const childMenus = itemObj.childMenus || itemObj.children;
|
|
165
|
+
for (let c = 0; c < childMenus.length; c++) {
|
|
166
|
+
subHtml += `<li class="se-select-item" data-parent-index="${i}" data-child-index="${c}">${typeof childMenus[c] === 'string' ? childMenus[c] : childMenus[c].outerHTML}</li>`;
|
|
167
|
+
}
|
|
168
|
+
html +=
|
|
169
|
+
`<li class="se-select-item se-has-submenu" data-index="${i}">${menuContent}` +
|
|
170
|
+
`<span class="se-submenu-arrow">${this.#$.icons.menu_arrow_right}</span></li>`;
|
|
171
|
+
|
|
172
|
+
// `popover: manual` lifts the submenu into the top layer so it escapes any
|
|
173
|
+
// `overflow: hidden` on the form's ancestors (toolbar, dropdown panels, ...).
|
|
174
|
+
const subEl = dom.utils.createElement(
|
|
175
|
+
'DIV',
|
|
176
|
+
{ class: 'se-select-submenu', popover: 'manual', 'data-parent-index': String(i) },
|
|
177
|
+
`<ul class="se-list-basic se-list-checked">${subHtml}</ul>`,
|
|
178
|
+
);
|
|
179
|
+
this.#submenuData.set(i, { items: itemObj.children, menus: childMenus, element: subEl });
|
|
180
|
+
} else {
|
|
181
|
+
html += `<li class="se-select-item" data-index="${i}">${menuContent}</li>`;
|
|
182
|
+
}
|
|
100
183
|
}
|
|
101
184
|
this.#createFormat(html);
|
|
102
185
|
|
|
186
|
+
// append submenu elements to form (outside se-list-inner)
|
|
187
|
+
for (const [, data] of this.#submenuData) {
|
|
188
|
+
this.form.appendChild(data.element);
|
|
189
|
+
}
|
|
190
|
+
|
|
103
191
|
this.items = /** @type {Array<string|Node>} */ (items);
|
|
104
|
-
this.menus = Array.from(this.form.querySelectorAll('li'));
|
|
192
|
+
this.menus = Array.from(this.form.querySelectorAll('li[data-index]'));
|
|
105
193
|
this.menuLen = this.menus.length;
|
|
106
194
|
}
|
|
107
195
|
|
|
@@ -119,7 +207,9 @@ class SelectMenu {
|
|
|
119
207
|
*/
|
|
120
208
|
on(referElement, selectMethod, attr = {}) {
|
|
121
209
|
this.#refer = /** @type {HTMLElement} */ (referElement);
|
|
122
|
-
this.#keydownTarget =
|
|
210
|
+
this.#keydownTarget =
|
|
211
|
+
this.#keydownTargetOverride ||
|
|
212
|
+
(dom.check.isInputElement(referElement) ? referElement : this.#$.frameContext.get('_ww'));
|
|
123
213
|
this.#selectMethod = selectMethod;
|
|
124
214
|
|
|
125
215
|
let innerStyle = '';
|
|
@@ -129,7 +219,10 @@ class SelectMenu {
|
|
|
129
219
|
this.form = dom.utils.createElement(
|
|
130
220
|
'DIV',
|
|
131
221
|
{
|
|
132
|
-
class:
|
|
222
|
+
class:
|
|
223
|
+
'se-select-menu' +
|
|
224
|
+
(this.#textDirDiff === true ? ' se-rtl' : '') +
|
|
225
|
+
(attr.class ? ' ' + attr.class : ''),
|
|
133
226
|
style: attr.style || '',
|
|
134
227
|
},
|
|
135
228
|
'<div class="se-list-inner"' + (innerStyle ? ' style="' + innerStyle + '"' : '') + '></div>',
|
|
@@ -161,12 +254,46 @@ class SelectMenu {
|
|
|
161
254
|
this.#addEvents();
|
|
162
255
|
this.#addGlobalEvent();
|
|
163
256
|
const positionItems = position ? position.split('-') : [];
|
|
164
|
-
const mainPosition =
|
|
165
|
-
|
|
257
|
+
const mainPosition =
|
|
258
|
+
positionItems[0] ||
|
|
259
|
+
(this.#textDirDiff !== null && this.#textDirDiff !== this.#$.options.get('_rtl')
|
|
260
|
+
? this.#dirPosition
|
|
261
|
+
: this.position);
|
|
262
|
+
const subPosition =
|
|
263
|
+
positionItems[1] ||
|
|
264
|
+
(this.#textDirDiff !== null && this.#textDirDiff !== this.#$.options.get('_rtl')
|
|
265
|
+
? this.#dirSubPosition
|
|
266
|
+
: this.subPosition);
|
|
267
|
+
this.#lastMainPosition = mainPosition;
|
|
268
|
+
this.#lastSubPosition = subPosition;
|
|
166
269
|
this.#setPosition(mainPosition, subPosition, onItemQuerySelector);
|
|
167
270
|
this.isOpen = true;
|
|
168
271
|
}
|
|
169
272
|
|
|
273
|
+
/**
|
|
274
|
+
* @description Re-runs positioning using the same direction the menu was opened with.
|
|
275
|
+
* Use when the reference element has moved (e.g. scroll repositioned the trigger) but
|
|
276
|
+
* the menu should stay open and follow.
|
|
277
|
+
*/
|
|
278
|
+
reposition() {
|
|
279
|
+
if (!this.isOpen || !this.#lastMainPosition) return;
|
|
280
|
+
this.#setPosition(this.#lastMainPosition, this.#lastSubPosition);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @description Soft-hide / soft-show without changing open state.
|
|
285
|
+
* close listeners (outside click, ESC) keep working, but is visually hidden until the trigger comes back.
|
|
286
|
+
*/
|
|
287
|
+
setHidden(hidden) {
|
|
288
|
+
if (!this.isOpen) return;
|
|
289
|
+
if (hidden) {
|
|
290
|
+
this.#closeSubmenu();
|
|
291
|
+
this.form.style.display = 'none';
|
|
292
|
+
} else {
|
|
293
|
+
this.reposition();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
170
297
|
/**
|
|
171
298
|
* @description Select menu close
|
|
172
299
|
*/
|
|
@@ -197,6 +324,198 @@ class SelectMenu {
|
|
|
197
324
|
this.#selectItem(index);
|
|
198
325
|
}
|
|
199
326
|
|
|
327
|
+
/**
|
|
328
|
+
* @description Whether a native submenu is currently open. Used by owners (e.g. a Controller) to
|
|
329
|
+
* let ESC dismiss only the open submenu instead of the whole menu.
|
|
330
|
+
* @returns {boolean}
|
|
331
|
+
*/
|
|
332
|
+
hasOpenSubmenu() {
|
|
333
|
+
return this.#activeSubmenuIndex > -1;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @description Opens the submenu for the given parent index.
|
|
338
|
+
* @param {number} parentIndex
|
|
339
|
+
*/
|
|
340
|
+
#openSubmenu(parentIndex) {
|
|
341
|
+
if (this.#activeSubmenuIndex === parentIndex) return;
|
|
342
|
+
this.#closeSubmenu();
|
|
343
|
+
|
|
344
|
+
const parentLi = this.menus[parentIndex];
|
|
345
|
+
if (!parentLi || !dom.utils.hasClass(parentLi, 'se-has-submenu')) return;
|
|
346
|
+
|
|
347
|
+
dom.utils.addClass(parentLi, 'se-submenu-open');
|
|
348
|
+
this.#activeSubmenuIndex = parentIndex;
|
|
349
|
+
this.#submenuItemIndex = -1;
|
|
350
|
+
this.#inSubmenu = false;
|
|
351
|
+
|
|
352
|
+
const data = this.#submenuData.get(parentIndex);
|
|
353
|
+
const sub = data?.element;
|
|
354
|
+
if (sub) {
|
|
355
|
+
// Show first so the top-layer containing block (viewport) is in effect when we
|
|
356
|
+
const supportsPopover = typeof sub.showPopover === 'function';
|
|
357
|
+
sub.style.visibility = 'hidden';
|
|
358
|
+
|
|
359
|
+
if (supportsPopover) {
|
|
360
|
+
if (!sub.matches(':popover-open')) sub.showPopover();
|
|
361
|
+
} else {
|
|
362
|
+
sub.style.display = 'block';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.#positionSubmenu(parentLi, sub, supportsPopover);
|
|
366
|
+
sub.style.visibility = '';
|
|
367
|
+
|
|
368
|
+
// Submenu is top-layer popover (viewport-fixed), but the parent form is in document
|
|
369
|
+
// flow (absolute) and scrolls with the page/container. Re-run positioning on scroll
|
|
370
|
+
// so the submenu stays anchored to the parent LI as it moves.
|
|
371
|
+
this.#bindSubmenuReposition = this.#$.eventManager.addGlobalEvent(
|
|
372
|
+
'scroll',
|
|
373
|
+
this.#globalEventHandlers.submenuReposition,
|
|
374
|
+
true,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* @description Position the submenu element next to the parent menu
|
|
381
|
+
* @param {HTMLElement} parentLi
|
|
382
|
+
* @param {HTMLElement} sub
|
|
383
|
+
* @param {boolean} supportsPopover
|
|
384
|
+
*/
|
|
385
|
+
#positionSubmenu(parentLi, sub, supportsPopover) {
|
|
386
|
+
// In the top layer the containing block is the viewport; otherwise it's the form.
|
|
387
|
+
const formRect = supportsPopover ? { top: 0, left: 0 } : this.form.getBoundingClientRect();
|
|
388
|
+
|
|
389
|
+
const parentRect = parentLi.getBoundingClientRect();
|
|
390
|
+
const subW = sub.offsetWidth;
|
|
391
|
+
const subH = sub.offsetHeight;
|
|
392
|
+
const vpW = _w.innerWidth;
|
|
393
|
+
const vpH = _w.innerHeight;
|
|
394
|
+
|
|
395
|
+
// Horizontal: open in the arrow's direction — RTL prefers left of parent, LTR prefers
|
|
396
|
+
// right. Flip to the opposite side if the preferred side overflows; if both overflow,
|
|
397
|
+
// keep the smaller-overflow side and shift inward by exactly that amount.
|
|
398
|
+
const rightVP = parentRect.right;
|
|
399
|
+
const leftVP = parentRect.left - subW;
|
|
400
|
+
const rightOverflow = Math.max(0, rightVP + subW - vpW);
|
|
401
|
+
const leftOverflow = Math.max(0, -leftVP);
|
|
402
|
+
const preferLeft = this.#textDirDiff === true;
|
|
403
|
+
|
|
404
|
+
let leftPx;
|
|
405
|
+
if (preferLeft) {
|
|
406
|
+
if (leftOverflow === 0) leftPx = leftVP;
|
|
407
|
+
else if (rightOverflow === 0) leftPx = rightVP;
|
|
408
|
+
else if (leftOverflow <= rightOverflow) leftPx = leftVP + leftOverflow;
|
|
409
|
+
else leftPx = rightVP - rightOverflow;
|
|
410
|
+
} else {
|
|
411
|
+
if (rightOverflow === 0) leftPx = rightVP;
|
|
412
|
+
else if (leftOverflow === 0) leftPx = leftVP;
|
|
413
|
+
else if (rightOverflow <= leftOverflow) leftPx = rightVP - rightOverflow;
|
|
414
|
+
else leftPx = leftVP + leftOverflow;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
sub.style.left = leftPx - formRect.left + 'px';
|
|
418
|
+
sub.style.right = '';
|
|
419
|
+
|
|
420
|
+
// Vertical: try top-aligned with parent; flip to bottom-anchored if it overflows
|
|
421
|
+
// downward; if both directions overflow (submenu taller than viewport), keep the
|
|
422
|
+
// side with the smaller overflow and shift inward by exactly that amount.
|
|
423
|
+
const topVP = parentRect.top;
|
|
424
|
+
const bottomVP = parentRect.bottom - subH;
|
|
425
|
+
const downOverflow = Math.max(0, topVP + subH - vpH);
|
|
426
|
+
const upOverflow = Math.max(0, -bottomVP);
|
|
427
|
+
|
|
428
|
+
let topPx;
|
|
429
|
+
if (downOverflow === 0) {
|
|
430
|
+
topPx = topVP;
|
|
431
|
+
} else if (upOverflow === 0) {
|
|
432
|
+
topPx = bottomVP;
|
|
433
|
+
} else if (downOverflow <= upOverflow) {
|
|
434
|
+
topPx = topVP - downOverflow;
|
|
435
|
+
} else {
|
|
436
|
+
topPx = bottomVP + upOverflow;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
sub.style.top = topPx - formRect.top + 'px';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @description Scroll/resize callback while submenu is open — re-runs positioning so the
|
|
444
|
+
* top-layer popover follows the parent LI which scrolls with the document.
|
|
445
|
+
*/
|
|
446
|
+
#OnSubmenuReposition() {
|
|
447
|
+
if (this.#activeSubmenuIndex < 0) return;
|
|
448
|
+
const data = this.#submenuData.get(this.#activeSubmenuIndex);
|
|
449
|
+
const sub = data?.element;
|
|
450
|
+
const parentLi = this.menus[this.#activeSubmenuIndex];
|
|
451
|
+
if (!sub || !parentLi) return;
|
|
452
|
+
this.#positionSubmenu(parentLi, sub, typeof sub.showPopover === 'function');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @description Closes any open submenu.
|
|
457
|
+
*/
|
|
458
|
+
#closeSubmenu() {
|
|
459
|
+
if (this.#submenuHoverTimer) {
|
|
460
|
+
_w.clearTimeout(this.#submenuHoverTimer);
|
|
461
|
+
this.#submenuHoverTimer = null;
|
|
462
|
+
}
|
|
463
|
+
this.#bindSubmenuReposition &&= this.#$.eventManager.removeGlobalEvent(this.#bindSubmenuReposition);
|
|
464
|
+
if (this.#activeSubmenuIndex > -1) {
|
|
465
|
+
const parentLi = this.menus[this.#activeSubmenuIndex];
|
|
466
|
+
if (parentLi) dom.utils.removeClass(parentLi, 'se-submenu-open');
|
|
467
|
+
|
|
468
|
+
const data = this.#submenuData.get(this.#activeSubmenuIndex);
|
|
469
|
+
if (data?.element) {
|
|
470
|
+
if (typeof data.element.hidePopover === 'function') {
|
|
471
|
+
if (data.element.matches(':popover-open')) data.element.hidePopover();
|
|
472
|
+
} else {
|
|
473
|
+
data.element.style.display = '';
|
|
474
|
+
}
|
|
475
|
+
dom.utils.removeClass(data.element.querySelectorAll('.se-select-item'), 'se-select-cursor');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
this.#activeSubmenuIndex = -1;
|
|
479
|
+
this.#submenuItemIndex = -1;
|
|
480
|
+
this.#inSubmenu = false;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* @description Selects a child item from an open submenu.
|
|
485
|
+
* @param {number} parentIndex
|
|
486
|
+
* @param {number} childIndex
|
|
487
|
+
*/
|
|
488
|
+
#selectChild(parentIndex, childIndex) {
|
|
489
|
+
const data = this.#submenuData.get(parentIndex);
|
|
490
|
+
if (!data) return;
|
|
491
|
+
if (this.checkList) {
|
|
492
|
+
const childLi = data.element?.querySelectorAll('.se-select-item')[childIndex];
|
|
493
|
+
if (childLi) dom.utils.toggleClass(childLi, 'se-checked');
|
|
494
|
+
}
|
|
495
|
+
this.#selectMethod(data.items[childIndex]);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* @description Moves selection within an open submenu.
|
|
500
|
+
* @param {number} num Direction (-1 up, +1 down)
|
|
501
|
+
*/
|
|
502
|
+
#moveSubmenuItem(num) {
|
|
503
|
+
const data = this.#submenuData.get(this.#activeSubmenuIndex);
|
|
504
|
+
if (!data?.element) return;
|
|
505
|
+
const items = data.element.querySelectorAll('.se-select-item');
|
|
506
|
+
const len = items.length;
|
|
507
|
+
if (!len) return;
|
|
508
|
+
|
|
509
|
+
num = this.#submenuItemIndex + num;
|
|
510
|
+
const idx = (this.#submenuItemIndex = num >= len ? 0 : num < 0 ? len - 1 : num);
|
|
511
|
+
|
|
512
|
+
dom.utils.removeClass(this.form, 'se-select-menu-mouse-move');
|
|
513
|
+
for (let i = 0; i < len; i++) {
|
|
514
|
+
if (i === idx) dom.utils.addClass(items[i], 'se-select-cursor');
|
|
515
|
+
else dom.utils.removeClass(items[i], 'se-select-cursor');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
200
519
|
/**
|
|
201
520
|
* @description Appends a formatted list of items to the menu.
|
|
202
521
|
* @param {string} html - The HTML string representing the menu items.
|
|
@@ -211,6 +530,7 @@ class SelectMenu {
|
|
|
211
530
|
#init() {
|
|
212
531
|
this.#removeEvents();
|
|
213
532
|
this.#removeGlobalEvent();
|
|
533
|
+
this.#closeSubmenu();
|
|
214
534
|
this.index = -1;
|
|
215
535
|
this.item = null;
|
|
216
536
|
if (this._onItem) {
|
|
@@ -241,9 +561,9 @@ class SelectMenu {
|
|
|
241
561
|
const len = this.menuLen;
|
|
242
562
|
for (let i = 0; i < len; i++) {
|
|
243
563
|
if (i === selectIndex) {
|
|
244
|
-
dom.utils.addClass(this.menus[i], '
|
|
564
|
+
dom.utils.addClass(this.menus[i], 'se-select-cursor');
|
|
245
565
|
} else {
|
|
246
|
-
dom.utils.removeClass(this.menus[i], '
|
|
566
|
+
dom.utils.removeClass(this.menus[i], 'se-select-cursor');
|
|
247
567
|
}
|
|
248
568
|
}
|
|
249
569
|
|
|
@@ -362,31 +682,28 @@ class SelectMenu {
|
|
|
362
682
|
}
|
|
363
683
|
|
|
364
684
|
form.style.left = l + 'px';
|
|
365
|
-
|
|
685
|
+
let fixedLeft = this.#$.offset.getGlobal(form).fixedLeft;
|
|
366
686
|
let overW = 0;
|
|
367
687
|
switch (side + '-' + (side ? originP : subPosition)) {
|
|
368
688
|
case 'true-left':
|
|
369
|
-
|
|
370
|
-
if (overW < 0) l = l = targetL + targetW + 1;
|
|
689
|
+
if (fixedLeft < 0) l = targetL + targetW + 1;
|
|
371
690
|
break;
|
|
372
691
|
case 'true-right':
|
|
373
|
-
|
|
374
|
-
if (overW < 0) l = targetL - formW - 1;
|
|
692
|
+
if (fixedLeft + formW > _w.innerWidth) l = targetL - formW - 1;
|
|
375
693
|
break;
|
|
376
|
-
case 'false-center':
|
|
377
|
-
overW = _w.innerWidth - (
|
|
694
|
+
case 'false-center':
|
|
695
|
+
overW = _w.innerWidth - (fixedLeft + formW);
|
|
378
696
|
if (overW < 0) l += overW - 4;
|
|
379
697
|
form.style.left = l + 'px';
|
|
380
|
-
|
|
381
|
-
if (
|
|
698
|
+
fixedLeft = this.#$.offset.getGlobal(form).fixedLeft;
|
|
699
|
+
if (fixedLeft < 0) l -= fixedLeft - 4;
|
|
382
700
|
break;
|
|
383
|
-
}
|
|
384
701
|
case 'false-left':
|
|
385
|
-
overW = _w.innerWidth - (
|
|
702
|
+
overW = _w.innerWidth - (fixedLeft + formW);
|
|
386
703
|
if (overW < 0) l += overW - 4;
|
|
387
704
|
break;
|
|
388
705
|
case 'false-right':
|
|
389
|
-
if (
|
|
706
|
+
if (fixedLeft < 0) l -= fixedLeft - 4;
|
|
390
707
|
break;
|
|
391
708
|
}
|
|
392
709
|
|
|
@@ -443,7 +760,11 @@ class SelectMenu {
|
|
|
443
760
|
#addGlobalEvent() {
|
|
444
761
|
this.#removeGlobalEvent();
|
|
445
762
|
this.#bindClose_key = this.#$.eventManager.addGlobalEvent('keydown', this.#globalEventHandlers.keydown, true);
|
|
446
|
-
this.#bindClose_mousedown = this.#$.eventManager.addGlobalEvent(
|
|
763
|
+
this.#bindClose_mousedown = this.#$.eventManager.addGlobalEvent(
|
|
764
|
+
'mousedown',
|
|
765
|
+
this.#globalEventHandlers.mousedown,
|
|
766
|
+
true,
|
|
767
|
+
);
|
|
447
768
|
}
|
|
448
769
|
|
|
449
770
|
/**
|
|
@@ -459,6 +780,45 @@ class SelectMenu {
|
|
|
459
780
|
* @param {KeyboardEvent} e - Event object
|
|
460
781
|
*/
|
|
461
782
|
#OnKeyDown_refer(e) {
|
|
783
|
+
// submenu keyboard navigation
|
|
784
|
+
if (this.#inSubmenu) {
|
|
785
|
+
switch (e.code) {
|
|
786
|
+
case 'ArrowUp':
|
|
787
|
+
e.preventDefault();
|
|
788
|
+
e.stopPropagation();
|
|
789
|
+
this.#moveSubmenuItem(-1);
|
|
790
|
+
return;
|
|
791
|
+
case 'ArrowDown':
|
|
792
|
+
e.preventDefault();
|
|
793
|
+
e.stopPropagation();
|
|
794
|
+
this.#moveSubmenuItem(1);
|
|
795
|
+
return;
|
|
796
|
+
case 'ArrowLeft':
|
|
797
|
+
e.preventDefault();
|
|
798
|
+
e.stopPropagation();
|
|
799
|
+
// exit submenu back to parent
|
|
800
|
+
this.#inSubmenu = false;
|
|
801
|
+
this.#submenuItemIndex = -1;
|
|
802
|
+
{
|
|
803
|
+
const subData = this.#submenuData.get(this.#activeSubmenuIndex);
|
|
804
|
+
if (subData?.element)
|
|
805
|
+
dom.utils.removeClass(
|
|
806
|
+
subData.element.querySelectorAll('.se-select-item'),
|
|
807
|
+
'se-select-cursor',
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
case 'Enter':
|
|
812
|
+
case 'Space':
|
|
813
|
+
if (this.#submenuItemIndex > -1) {
|
|
814
|
+
e.preventDefault();
|
|
815
|
+
e.stopPropagation();
|
|
816
|
+
this.#selectChild(this.#activeSubmenuIndex, this.#submenuItemIndex);
|
|
817
|
+
}
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
462
822
|
let moveIndex;
|
|
463
823
|
switch (e.code) {
|
|
464
824
|
case 'ArrowUp': // up
|
|
@@ -486,9 +846,15 @@ class SelectMenu {
|
|
|
486
846
|
e.stopPropagation();
|
|
487
847
|
moveIndex = -1;
|
|
488
848
|
break;
|
|
489
|
-
case 'ArrowRight': //right
|
|
849
|
+
case 'ArrowRight': // right — enter submenu if available
|
|
490
850
|
e.preventDefault();
|
|
491
851
|
e.stopPropagation();
|
|
852
|
+
if (this.index > -1 && this.#submenuData.has(this.index)) {
|
|
853
|
+
this.#openSubmenu(this.index);
|
|
854
|
+
this.#inSubmenu = true;
|
|
855
|
+
this.#moveSubmenuItem(1);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
492
858
|
moveIndex = 1;
|
|
493
859
|
break;
|
|
494
860
|
case 'Enter':
|
|
@@ -496,14 +862,21 @@ class SelectMenu {
|
|
|
496
862
|
if (this.index > -1) {
|
|
497
863
|
e.preventDefault();
|
|
498
864
|
e.stopPropagation();
|
|
499
|
-
this.#
|
|
500
|
-
|
|
501
|
-
|
|
865
|
+
if (this.#submenuData.has(this.index)) {
|
|
866
|
+
this.#openSubmenu(this.index);
|
|
867
|
+
this.#inSubmenu = true;
|
|
868
|
+
this.#moveSubmenuItem(1);
|
|
869
|
+
} else {
|
|
870
|
+
this.#select(this.index);
|
|
871
|
+
}
|
|
502
872
|
}
|
|
503
873
|
break;
|
|
504
874
|
}
|
|
505
875
|
|
|
506
|
-
if (moveIndex)
|
|
876
|
+
if (moveIndex) {
|
|
877
|
+
this.#closeSubmenu();
|
|
878
|
+
this.#moveItem(moveIndex);
|
|
879
|
+
}
|
|
507
880
|
}
|
|
508
881
|
|
|
509
882
|
/**
|
|
@@ -523,9 +896,40 @@ class SelectMenu {
|
|
|
523
896
|
#OnMouseMove_list(e) {
|
|
524
897
|
const eventTarget = dom.query.getEventTarget(e);
|
|
525
898
|
dom.utils.addClass(this.form, 'se-select-menu-mouse-move');
|
|
526
|
-
|
|
899
|
+
|
|
900
|
+
// `data-*` attrs live on `<li>`, but events may originate from nested children
|
|
901
|
+
// (e.g. CommandMenu wraps row content in a `<button>` for native :disabled). Walk
|
|
902
|
+
// up so the lookup still works when the target isn't the `<li>` itself.
|
|
903
|
+
const childTarget = eventTarget.closest?.('[data-child-index]');
|
|
904
|
+
const childIndex = childTarget?.getAttribute('data-child-index');
|
|
905
|
+
if (childIndex !== null && childIndex !== undefined) {
|
|
906
|
+
this.#submenuItemIndex = Number(childIndex);
|
|
907
|
+
const subData = this.#submenuData.get(this.#activeSubmenuIndex);
|
|
908
|
+
if (subData?.element) {
|
|
909
|
+
const items = subData.element.querySelectorAll('.se-select-item');
|
|
910
|
+
dom.utils.removeClass(items, 'se-select-cursor');
|
|
911
|
+
dom.utils.addClass(items[this.#submenuItemIndex], 'se-select-cursor');
|
|
912
|
+
}
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const indexTarget = eventTarget.closest?.('[data-index]');
|
|
917
|
+
const index = indexTarget?.getAttribute('data-index');
|
|
527
918
|
if (!index) return;
|
|
528
|
-
|
|
919
|
+
const numIndex = Number(index);
|
|
920
|
+
this.index = numIndex;
|
|
921
|
+
|
|
922
|
+
// submenu hover logic
|
|
923
|
+
if (this.#submenuData.has(numIndex)) {
|
|
924
|
+
if (this.#submenuHoverTimer) _w.clearTimeout(this.#submenuHoverTimer);
|
|
925
|
+
this.#openSubmenu(numIndex);
|
|
926
|
+
} else if (this.#activeSubmenuIndex > -1) {
|
|
927
|
+
// check if mouse is inside the active submenu
|
|
928
|
+
const activeData = this.#submenuData.get(this.#activeSubmenuIndex);
|
|
929
|
+
if (!activeData?.element?.contains(eventTarget)) {
|
|
930
|
+
this.#closeSubmenu();
|
|
931
|
+
}
|
|
932
|
+
}
|
|
529
933
|
}
|
|
530
934
|
|
|
531
935
|
/**
|
|
@@ -534,14 +938,37 @@ class SelectMenu {
|
|
|
534
938
|
#OnClick_list(e) {
|
|
535
939
|
let target = dom.query.getEventTarget(e);
|
|
536
940
|
let index = null;
|
|
941
|
+
let childIndex = null;
|
|
537
942
|
|
|
538
|
-
while (!index && !/UL/i.test(target.tagName) && !dom.utils.hasClass(target, 'se-select-menu')) {
|
|
539
|
-
|
|
943
|
+
while (!index && !childIndex && !/UL/i.test(target.tagName) && !dom.utils.hasClass(target, 'se-select-menu')) {
|
|
944
|
+
childIndex = target.getAttribute('data-child-index');
|
|
945
|
+
if (!childIndex) index = target.getAttribute('data-index');
|
|
540
946
|
target = target.parentElement;
|
|
541
947
|
}
|
|
542
948
|
|
|
949
|
+
// child item click
|
|
950
|
+
if (childIndex !== null) {
|
|
951
|
+
let parentTarget = target;
|
|
952
|
+
let parentIndex = null;
|
|
953
|
+
while (!parentIndex && parentTarget) {
|
|
954
|
+
parentIndex = parentTarget.getAttribute('data-parent-index') || parentTarget.getAttribute('data-index');
|
|
955
|
+
parentTarget = parentTarget.parentElement;
|
|
956
|
+
}
|
|
957
|
+
if (parentIndex !== null) this.#selectChild(Number(parentIndex), Number(childIndex));
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
|
|
543
961
|
if (!index) return;
|
|
544
|
-
|
|
962
|
+
const numIndex = Number(index);
|
|
963
|
+
|
|
964
|
+
// parent with children — toggle submenu instead of selecting
|
|
965
|
+
if (this.#submenuData.has(numIndex)) {
|
|
966
|
+
if (this.#activeSubmenuIndex === numIndex) this.#closeSubmenu();
|
|
967
|
+
else this.#openSubmenu(numIndex);
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
this.#select(numIndex);
|
|
545
972
|
}
|
|
546
973
|
|
|
547
974
|
/**
|
|
@@ -549,6 +976,24 @@ class SelectMenu {
|
|
|
549
976
|
*/
|
|
550
977
|
#CloseListener_key(e) {
|
|
551
978
|
if (!keyCodeMap.isEsc(e.code)) return;
|
|
979
|
+
|
|
980
|
+
if (this.#activeSubmenuIndex > -1) {
|
|
981
|
+
e.preventDefault();
|
|
982
|
+
e.stopPropagation();
|
|
983
|
+
|
|
984
|
+
const parentIndex = this.#activeSubmenuIndex;
|
|
985
|
+
this.#closeSubmenu();
|
|
986
|
+
this.#selectItem(parentIndex);
|
|
987
|
+
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
if (this.subEscMethod?.()) {
|
|
992
|
+
e.preventDefault();
|
|
993
|
+
e.stopPropagation();
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
|
|
552
997
|
this.close();
|
|
553
998
|
}
|
|
554
999
|
|
package/src/modules/ui/index.js
CHANGED
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
// UI Modules - Helper classes, no callbacks required
|
|
3
3
|
// ============================================================
|
|
4
4
|
export { default as _DragHandle } from './_DragHandle.js';
|
|
5
|
+
export { default as CommandMenu } from './CommandMenu.js';
|
|
5
6
|
export { default as ModalAnchorEditor } from './ModalAnchorEditor.js';
|
|
6
7
|
export { default as SelectMenu } from './SelectMenu.js';
|