1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
4 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
5 | return c > 3 && r && Object.defineProperty(target, key, r), r;
|
6 | };
|
7 | var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
8 | if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
9 | if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
10 | return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
11 | };
|
12 | var _DuoyunMenuElement_instances, _DuoyunMenuElement_width_get, _DuoyunMenuElement_menuEles_get, _DuoyunMenuElement_offset, _DuoyunMenuElement_onEnterMenu, _DuoyunMenuElement_execHandle, _DuoyunMenuElement_onKeydown, _DuoyunMenuElement_preventDefault, _DuoyunMenuElement_initPosition, _DuoyunMenuElement_onMaskClick;
|
13 | import { connectStore, adoptedStyle, customElement } from '@mantou/gem/lib/decorators';
|
14 | import { GemElement, html } from '@mantou/gem/lib/element';
|
15 | import { createCSSSheet, css, styleMap, classMap } from '@mantou/gem/lib/utils';
|
16 | import { createStore, updateStore } from '@mantou/gem/lib/store';
|
17 | import { icons } from '../lib/icons';
|
18 | import { locale } from '../lib/locale';
|
19 | import { setBodyInert } from '../lib/utils';
|
20 | import { hotkeys } from '../lib/hotkeys';
|
21 | import { theme } from '../lib/theme';
|
22 | import { toggleActiveState } from '../lib/element';
|
23 | import './compartment';
|
24 | import './button';
|
25 | import './options';
|
26 | export const menuStore = createStore({
|
27 | menuStack: [],
|
28 | });
|
29 | let closeResolve;
|
30 | const style = createCSSSheet(css `
|
31 | :host(:where(:not([hidden]))) {
|
32 | display: block;
|
33 | position: fixed;
|
34 | z-index: calc(${theme.popupZIndex} + 2);
|
35 | inset: 0;
|
36 | top: env(titlebar-area-height, var(--titlebar-area-height, 0px));
|
37 | font-size: 0.875em;
|
38 | cursor: default;
|
39 | user-select: none;
|
40 | }
|
41 | .mask {
|
42 | position: absolute;
|
43 | inset: 0;
|
44 | }
|
45 | .opaque {
|
46 | background: rgba(0, 0, 0, ${theme.maskAlpha});
|
47 | }
|
48 | .menu-custom-container {
|
49 | padding: 0.4em 1em;
|
50 | }
|
51 | .menu {
|
52 | outline: none;
|
53 | position: fixed;
|
54 | font-size: 1em;
|
55 | box-sizing: border-box;
|
56 | box-shadow: 0 0.3em 1em rgba(0, 0, 0, calc(${theme.maskAlpha} - 0.1));
|
57 | scrollbar-width: none;
|
58 | }
|
59 | .menu::-webkit-scrollbar {
|
60 | width: 0;
|
61 | }
|
62 | `);
|
63 |
|
64 |
|
65 |
|
66 | let DuoyunMenuElement = class DuoyunMenuElement extends GemElement {
|
67 | constructor() {
|
68 | super({ delegatesFocus: true });
|
69 | _DuoyunMenuElement_instances.add(this);
|
70 | _DuoyunMenuElement_offset.set(this, 4);
|
71 | _DuoyunMenuElement_onEnterMenu.set(this, (evt, menuStackIndex, subMenu) => {
|
72 | const { menuStack, openLeft } = menuStore;
|
73 | if (subMenu) {
|
74 | const itemEle = evt.currentTarget;
|
75 | const { left, right, top, bottom, width } = itemEle.getBoundingClientRect();
|
76 | const em = parseFloat(getComputedStyle(this).fontSize);
|
77 | const expectX = right - 0.75 * em;
|
78 | const expectY = top - 0.4 * em;
|
79 | const openTop = expectY > innerHeight - 150;
|
80 | const isToLeft = (right + width > innerWidth ||
|
81 | openLeft ||
|
82 | (menuStackIndex > 0 && menuStack[menuStackIndex].x < menuStack[menuStackIndex - 1].x)) &&
|
83 | left > 300;
|
84 | updateStore(menuStore, {
|
85 | menuStack: [
|
86 | ...menuStack.slice(0, menuStackIndex + 1),
|
87 | {
|
88 | openTop,
|
89 | x: isToLeft ? left - width + 0.75 * em : expectX,
|
90 | y: openTop ? innerHeight - bottom - 0.4 * em : expectY,
|
91 | menu: subMenu,
|
92 | },
|
93 | ],
|
94 | });
|
95 | }
|
96 | else {
|
97 | updateStore(menuStore, {
|
98 | menuStack: menuStack.slice(0, menuStackIndex + 1),
|
99 | });
|
100 | }
|
101 | });
|
102 | _DuoyunMenuElement_execHandle.set(this, (handle) => {
|
103 | if (handle) {
|
104 | ContextMenu.close();
|
105 | handle();
|
106 | }
|
107 | });
|
108 | _DuoyunMenuElement_onKeydown.set(this, (evt, menuStackIndex) => {
|
109 | evt.stopPropagation();
|
110 | const focusPrevMenu = () => {
|
111 | var _a;
|
112 | updateStore(menuStore, {
|
113 | menuStack: menuStore.menuStack.slice(0, menuStackIndex),
|
114 | });
|
115 | (_a = __classPrivateFieldGet(this, _DuoyunMenuElement_instances, "a", _DuoyunMenuElement_menuEles_get)[menuStackIndex - 1]) === null || _a === void 0 ? void 0 : _a.focus();
|
116 | };
|
117 | hotkeys({
|
118 | esc: menuStackIndex === 0 ? ContextMenu.close : focusPrevMenu,
|
119 | left: focusPrevMenu,
|
120 | right: () => { var _a; return (_a = __classPrivateFieldGet(this, _DuoyunMenuElement_instances, "a", _DuoyunMenuElement_menuEles_get)[menuStackIndex + 1]) === null || _a === void 0 ? void 0 : _a.focus(); },
|
121 | })(evt);
|
122 | });
|
123 | _DuoyunMenuElement_preventDefault.set(this, (evt) => {
|
124 | evt.preventDefault();
|
125 | });
|
126 | _DuoyunMenuElement_initPosition.set(this, async () => {
|
127 |
|
128 | await Promise.resolve();
|
129 | const element = __classPrivateFieldGet(this, _DuoyunMenuElement_instances, "a", _DuoyunMenuElement_menuEles_get).shift();
|
130 | const { activeElement, openLeft, menuStack, maxHeight } = menuStore;
|
131 | const { scrollHeight, clientHeight, clientWidth } = element;
|
132 | const menu = menuStack[0];
|
133 | const height = scrollHeight + 2;
|
134 | const width = clientWidth + 2;
|
135 | if (activeElement) {
|
136 | const { left, right, top, bottom } = activeElement.getBoundingClientRect();
|
137 | const showToLeft = openLeft ? left > width + __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f") : innerWidth - left < width + __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f");
|
138 | const showToTop = innerHeight - bottom < height + 2 * __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f") && top > innerHeight - bottom;
|
139 | const x = showToLeft ? right - width : left;
|
140 | const y = showToTop ? Math.max(top - height - 2 * __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f"), 0) : bottom;
|
141 | updateStore(menuStore, {
|
142 | maxHeight: maxHeight || `${showToTop ? top - 2 * __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f") : innerHeight - bottom - 2 * __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f")}px`,
|
143 | menuStack: [{ ...menu, x, y }],
|
144 | });
|
145 | }
|
146 | else {
|
147 | const y = innerHeight - menu.y > width ? menu.y : Math.max(0, menu.y - (scrollHeight - clientHeight));
|
148 | updateStore(menuStore, { menuStack: [{ ...menu, y }] });
|
149 | }
|
150 | });
|
151 | _DuoyunMenuElement_onMaskClick.set(this, () => {
|
152 | if (menuStore.maskClosable) {
|
153 | ContextMenu.close();
|
154 | }
|
155 | });
|
156 | this.mounted = () => {
|
157 | var _a;
|
158 | __classPrivateFieldGet(this, _DuoyunMenuElement_initPosition, "f").call(this);
|
159 | (_a = __classPrivateFieldGet(this, _DuoyunMenuElement_instances, "a", _DuoyunMenuElement_menuEles_get).shift()) === null || _a === void 0 ? void 0 : _a.focus();
|
160 | const restoreInert = setBodyInert(this);
|
161 | ContextMenu.instance = this;
|
162 | this.addEventListener('contextmenu', __classPrivateFieldGet(this, _DuoyunMenuElement_preventDefault, "f"));
|
163 | return () => {
|
164 | restoreInert();
|
165 | ContextMenu.instance = undefined;
|
166 | };
|
167 | };
|
168 | this.render = () => {
|
169 | const { menuStack, maxHeight, maskClosable } = menuStore;
|
170 | return html `
|
171 | <div class=${classMap({ mask: true, opaque: !maskClosable })} @click=${__classPrivateFieldGet(this, _DuoyunMenuElement_onMaskClick, "f")}></div>
|
172 | ${menuStack.map(({ x, y, menu, searchable, openTop, header }, index, _, calcWidth = __classPrivateFieldGet(this, _DuoyunMenuElement_instances, "a", _DuoyunMenuElement_width_get) === 'auto' ? '0px' : __classPrivateFieldGet(this, _DuoyunMenuElement_instances, "a", _DuoyunMenuElement_width_get)) => html `
|
173 | <dy-options
|
174 | class="menu"
|
175 | style=${styleMap({
|
176 | width: __classPrivateFieldGet(this, _DuoyunMenuElement_instances, "a", _DuoyunMenuElement_width_get),
|
177 | maxHeight: openTop
|
178 | ? '20em'
|
179 | : maxHeight && index === 0
|
180 | ? `${maxHeight}`
|
181 | : `calc(100vh - 0.8em - ${y - __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f")}px)`,
|
182 | [openTop ? 'bottom' : 'top']: `${y + __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f")}px`,
|
183 | left: `min(${x}px, calc(100vw - ${calcWidth} - ${2 * __classPrivateFieldGet(this, _DuoyunMenuElement_offset, "f")}px))`,
|
184 | })}
|
185 | @keydown=${(evt) => __classPrivateFieldGet(this, _DuoyunMenuElement_onKeydown, "f").call(this, evt, index)}
|
186 | ?searchable=${searchable}
|
187 | .options=${Array.isArray(menu)
|
188 | ? menu.map(({ text, description, tag, tagIcon, handle, disabled, selected, danger, menu: subMenu }, _index, __arr, onPointerEnter = (evt) => __classPrivateFieldGet(this, _DuoyunMenuElement_onEnterMenu, "f").call(this, evt, index, subMenu), onClick = !disabled ? (_evt) => __classPrivateFieldGet(this, _DuoyunMenuElement_execHandle, "f").call(this, handle) : undefined) => {
|
189 | var _a;
|
190 | return ({
|
191 | label: text,
|
192 | description,
|
193 | tag,
|
194 | disabled,
|
195 | danger,
|
196 | highlight: !!(subMenu && subMenu === ((_a = menuStack[index + 1]) === null || _a === void 0 ? void 0 : _a.menu)),
|
197 | tagIcon: subMenu ? icons.right : selected ? icons.check : tagIcon,
|
198 | onClick: subMenu ? onPointerEnter : onClick,
|
199 | onPointerEnter,
|
200 | });
|
201 | })
|
202 | : undefined}
|
203 | >
|
204 | ${Array.isArray(menu)
|
205 | ? header
|
206 | : html `
|
207 | <div class="menu-custom-container">
|
208 | <dy-compartment .content=${menu}></dy-compartment>
|
209 | </div>
|
210 | `}
|
211 | </dy-options>
|
212 | `)}
|
213 | `;
|
214 | };
|
215 | document.body.append(this);
|
216 | }
|
217 | static async open(menu, options = {}) {
|
218 | var _a;
|
219 | const { activeElement, openLeft, x = 0, y = 0, width, maxHeight, searchable, header, maskClosable = true, } = options;
|
220 | if (Array.isArray(menu) && menu.length === 0)
|
221 | throw new Error('menu length is 0');
|
222 | toggleActiveState(activeElement, true);
|
223 | updateStore(menuStore, {
|
224 | width,
|
225 | maxHeight,
|
226 | activeElement,
|
227 | openLeft,
|
228 | maskClosable,
|
229 | menuStack: [{ x, y, menu, searchable, header }],
|
230 | });
|
231 | if (ContextMenu.instance) {
|
232 | await __classPrivateFieldGet((_a = ContextMenu.instance), _DuoyunMenuElement_initPosition, "f").call(_a);
|
233 | }
|
234 | else {
|
235 | new ContextMenu();
|
236 | }
|
237 | await new Promise((res) => (closeResolve = res));
|
238 | }
|
239 | static async confirm(text, options) {
|
240 | return new Promise((res, rej) => {
|
241 | const onClick = () => {
|
242 | ContextMenu.close();
|
243 | res(null);
|
244 | };
|
245 | ContextMenu.open(html `
|
246 | <style>
|
247 | .confirm-button {
|
248 | text-align: right;
|
249 | margin-top: 2em;
|
250 | }
|
251 | </style>
|
252 | <div class="confirm-text">${text}</div>
|
253 | <div class="confirm-button">
|
254 | <dy-button @click=${onClick} .color=${options.danger ? 'danger' : 'normal'}>
|
255 | ${options.okText || locale.ok}
|
256 | </dy-button>
|
257 | </div>
|
258 | `, options).then(rej);
|
259 | });
|
260 | }
|
261 | static close() {
|
262 | var _a, _b;
|
263 | if (!ContextMenu.instance)
|
264 | return;
|
265 | toggleActiveState(menuStore.activeElement, false);
|
266 | closeResolve();
|
267 | ContextMenu.instance.remove();
|
268 |
|
269 | (_a = menuStore.activeElement) === null || _a === void 0 ? void 0 : _a.focus();
|
270 | (_b = menuStore.activeElement) === null || _b === void 0 ? void 0 : _b.blur();
|
271 | }
|
272 | };
|
273 | _DuoyunMenuElement_offset = new WeakMap(), _DuoyunMenuElement_onEnterMenu = new WeakMap(), _DuoyunMenuElement_execHandle = new WeakMap(), _DuoyunMenuElement_onKeydown = new WeakMap(), _DuoyunMenuElement_preventDefault = new WeakMap(), _DuoyunMenuElement_initPosition = new WeakMap(), _DuoyunMenuElement_onMaskClick = new WeakMap(), _DuoyunMenuElement_instances = new WeakSet(), _DuoyunMenuElement_width_get = function _DuoyunMenuElement_width_get() {
|
274 | return menuStore.width || '15em';
|
275 | }, _DuoyunMenuElement_menuEles_get = function _DuoyunMenuElement_menuEles_get() {
|
276 | return [...this.shadowRoot.querySelectorAll('.menu')];
|
277 | };
|
278 | DuoyunMenuElement = __decorate([
|
279 | customElement('dy-menu'),
|
280 | connectStore(menuStore),
|
281 | adoptedStyle(style)
|
282 | ], DuoyunMenuElement);
|
283 | export { DuoyunMenuElement };
|
284 | export const ContextMenu = DuoyunMenuElement;
|
285 |
|
\ | No newline at end of file |