UNPKG

14.2 kBJavaScriptView Raw
1var __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};
7var __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};
12var _DuoyunMenuElement_instances, _DuoyunMenuElement_width_get, _DuoyunMenuElement_menuEles_get, _DuoyunMenuElement_offset, _DuoyunMenuElement_onEnterMenu, _DuoyunMenuElement_execHandle, _DuoyunMenuElement_onKeydown, _DuoyunMenuElement_preventDefault, _DuoyunMenuElement_initPosition, _DuoyunMenuElement_onMaskClick;
13import { connectStore, adoptedStyle, customElement } from '@mantou/gem/lib/decorators';
14import { GemElement, html } from '@mantou/gem/lib/element';
15import { createCSSSheet, css, styleMap, classMap } from '@mantou/gem/lib/utils';
16import { createStore, updateStore } from '@mantou/gem/lib/store';
17import { icons } from '../lib/icons';
18import { locale } from '../lib/locale';
19import { setBodyInert } from '../lib/utils';
20import { hotkeys } from '../lib/hotkeys';
21import { theme } from '../lib/theme';
22import { toggleActiveState } from '../lib/element';
23import './compartment';
24import './button';
25import './options';
26export const menuStore = createStore({
27 menuStack: [],
28});
29let closeResolve;
30const 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 * @customElement dy-menu
65 */
66let 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 // await `ContextMenu` content update
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 // specify keyboard navigation location
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};
278DuoyunMenuElement = __decorate([
279 customElement('dy-menu'),
280 connectStore(menuStore),
281 adoptedStyle(style)
282], DuoyunMenuElement);
283export { DuoyunMenuElement };
284export const ContextMenu = DuoyunMenuElement;
285//# sourceMappingURL=menu.js.map
\No newline at end of file