minterm 0.1.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/LICENSE +24 -0
- package/README.md +194 -0
- package/css/minterm.css +400 -0
- package/index.js +27 -0
- package/package.json +40 -0
- package/src/adapters/base-adapter.js +59 -0
- package/src/adapters/manual-adapter.js +14 -0
- package/src/adapters/nats-adapter.js +42 -0
- package/src/adapters/redis-adapter.js +83 -0
- package/src/adapters/rest-poller.js +42 -0
- package/src/adapters/websocket-adapter.js +59 -0
- package/src/arrow-overlay.js +250 -0
- package/src/emitter.js +30 -0
- package/src/formatters.js +41 -0
- package/src/modal-manager.js +70 -0
- package/src/themes.js +149 -0
- package/src/widgets/activity-bars.js +55 -0
- package/src/widgets/base-widget.js +49 -0
- package/src/widgets/message-log.js +81 -0
- package/src/widgets/mini-chart.js +144 -0
- package/src/widgets/range-bar.js +94 -0
- package/src/widgets/ticker.js +53 -0
- package/src/window-manager.js +425 -0
- package/src/z-index.js +35 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/** DOS-style draggable, resizable window manager */
|
|
2
|
+
|
|
3
|
+
import { Emitter } from './emitter.js';
|
|
4
|
+
import { getNextZ, bringToFront, cycleWindow } from './z-index.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
x: 50, y: 50, width: 400, height: null,
|
|
8
|
+
title: '', closable: false, lockable: false,
|
|
9
|
+
minWidth: 200, minHeight: 60,
|
|
10
|
+
classPrefix: 'mt',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class WindowManager extends Emitter {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} [opts]
|
|
16
|
+
* @param {HTMLElement} [opts.container=document.body] — parent for windows
|
|
17
|
+
* @param {string} [opts.classPrefix='mt'] — CSS class prefix
|
|
18
|
+
* @param {number} [opts.layoutSlots=5] — number of layout snapshot slots (0 to disable keybinds)
|
|
19
|
+
* @param {boolean} [opts.layoutBar=false] — show clickable layout bar UI
|
|
20
|
+
* @param {boolean} [opts.lockable=false] — show lock button on all windows by default
|
|
21
|
+
* @param {boolean} [opts.escapeClose=true] — bind Escape to closeAll()
|
|
22
|
+
*/
|
|
23
|
+
constructor(opts = {}) {
|
|
24
|
+
super();
|
|
25
|
+
this._container = opts.container || document.body;
|
|
26
|
+
this._pfx = opts.classPrefix || DEFAULTS.classPrefix;
|
|
27
|
+
this._lockable = opts.lockable ?? false;
|
|
28
|
+
this._escapeClose = opts.escapeClose ?? true;
|
|
29
|
+
this._windows = {};
|
|
30
|
+
this._dragState = null;
|
|
31
|
+
this._resizeState = null;
|
|
32
|
+
this._layouts = {};
|
|
33
|
+
this._layoutSlots = opts.layoutSlots ?? 5;
|
|
34
|
+
this._layoutBarEl = null;
|
|
35
|
+
this._bound = false;
|
|
36
|
+
this._bindEvents();
|
|
37
|
+
if (opts.layoutBar) this._createLayoutBar();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Create a window. Returns the window element. */
|
|
41
|
+
createWindow(id, opts = {}) {
|
|
42
|
+
if (this._windows[id]) return this._windows[id].el;
|
|
43
|
+
|
|
44
|
+
const o = { ...DEFAULTS, lockable: this._lockable, ...opts };
|
|
45
|
+
const pfx = this._pfx;
|
|
46
|
+
|
|
47
|
+
const win = document.createElement('div');
|
|
48
|
+
win.className = `${pfx}-win`;
|
|
49
|
+
win.id = `${pfx}-win-${id}`;
|
|
50
|
+
win.dataset.winId = id;
|
|
51
|
+
win.style.left = o.x + 'px';
|
|
52
|
+
win.style.top = o.y + 'px';
|
|
53
|
+
if (o.width) win.style.width = o.width + 'px';
|
|
54
|
+
if (o.height) win.style.height = o.height + 'px';
|
|
55
|
+
win.style.zIndex = getNextZ();
|
|
56
|
+
win.dataset.minW = o.minWidth;
|
|
57
|
+
win.dataset.minH = o.minHeight;
|
|
58
|
+
|
|
59
|
+
const lockBtn = o.lockable
|
|
60
|
+
? `<span class="${pfx}-win-lock" data-lock="${id}" title="Lock window"><svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><path d="M9 1C9 .4 8.6 0 8 0S7 .4 7 1v4L4.5 6.5 4 7v1.5l3-.5V15l1 1 1-1V8l3 .5V7l-.5-.5L9 5V1z"/></svg></span>`
|
|
61
|
+
: '';
|
|
62
|
+
|
|
63
|
+
win.innerHTML = `
|
|
64
|
+
<div class="${pfx}-win-titlebar" data-win="${id}">
|
|
65
|
+
<span class="${pfx}-win-title">${o.title}</span>
|
|
66
|
+
${lockBtn}${o.closable ? `<span class="${pfx}-win-close" data-close="${id}">\u2715</span>` : ''}
|
|
67
|
+
</div>
|
|
68
|
+
<div class="${pfx}-win-body" id="${pfx}-win-body-${id}"></div>
|
|
69
|
+
<div class="${pfx}-win-resize" data-win="${id}"></div>
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
this._container.appendChild(win);
|
|
73
|
+
this._windows[id] = { el: win, locked: false };
|
|
74
|
+
this.emit('window:create', { id, el: win });
|
|
75
|
+
return win;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
closeWindow(id) {
|
|
79
|
+
const w = this._windows[id];
|
|
80
|
+
if (!w) return;
|
|
81
|
+
w.el.remove();
|
|
82
|
+
delete this._windows[id];
|
|
83
|
+
this.emit('window:close', { id });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Close all closable, unlocked windows */
|
|
87
|
+
closeAll() {
|
|
88
|
+
for (const id of Object.keys(this._windows)) {
|
|
89
|
+
const w = this._windows[id];
|
|
90
|
+
if (w.locked) continue;
|
|
91
|
+
if (w.el.querySelector(`.${this._pfx}-win-close`)) {
|
|
92
|
+
this.closeWindow(id);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
setContent(id, title, html) {
|
|
98
|
+
const w = this._windows[id];
|
|
99
|
+
if (!w) return;
|
|
100
|
+
const pfx = this._pfx;
|
|
101
|
+
w.el.querySelector(`.${pfx}-win-title`).textContent = title;
|
|
102
|
+
w.el.querySelector(`.${pfx}-win-body`).innerHTML = html;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getBody(id) {
|
|
106
|
+
const w = this._windows[id];
|
|
107
|
+
return w ? w.el.querySelector(`.${this._pfx}-win-body`) : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Shorthand: create a window and mount a widget in one call.
|
|
112
|
+
* @param {string} id — window id
|
|
113
|
+
* @param {Function} WidgetClass — widget constructor (e.g. MiniChart)
|
|
114
|
+
* @param {object} [widgetOpts] — options forwarded to widget constructor
|
|
115
|
+
* @param {object} [windowOpts] — options forwarded to createWindow
|
|
116
|
+
* @returns {object} the widget instance
|
|
117
|
+
*/
|
|
118
|
+
addWidget(id, WidgetClass, widgetOpts = {}, windowOpts = {}) {
|
|
119
|
+
this.createWindow(id, windowOpts);
|
|
120
|
+
return new WidgetClass(this.getBody(id), widgetOpts);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getWindow(id) { return this._windows[id]?.el || null; }
|
|
124
|
+
has(id) { return !!this._windows[id]; }
|
|
125
|
+
getIds() { return Object.keys(this._windows); }
|
|
126
|
+
|
|
127
|
+
// ── Lock API ──
|
|
128
|
+
|
|
129
|
+
/** Lock a window — prevents drag and resize */
|
|
130
|
+
lockWindow(id) { this._setLocked(id, true); }
|
|
131
|
+
|
|
132
|
+
/** Unlock a window */
|
|
133
|
+
unlockWindow(id) { this._setLocked(id, false); }
|
|
134
|
+
|
|
135
|
+
/** Toggle lock state */
|
|
136
|
+
toggleLock(id) {
|
|
137
|
+
const w = this._windows[id];
|
|
138
|
+
if (w) this._setLocked(id, !w.locked);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Check if a window is locked */
|
|
142
|
+
isLocked(id) { return !!this._windows[id]?.locked; }
|
|
143
|
+
|
|
144
|
+
/** Show lock buttons on all windows that have them hidden */
|
|
145
|
+
showLockButtons() { this._toggleLockButtons(true); }
|
|
146
|
+
|
|
147
|
+
/** Hide lock buttons on all windows */
|
|
148
|
+
hideLockButtons() { this._toggleLockButtons(false); }
|
|
149
|
+
|
|
150
|
+
_setLocked(id, locked) {
|
|
151
|
+
const w = this._windows[id];
|
|
152
|
+
if (!w) return;
|
|
153
|
+
w.locked = locked;
|
|
154
|
+
const pfx = this._pfx;
|
|
155
|
+
const el = w.el;
|
|
156
|
+
if (locked) {
|
|
157
|
+
el.classList.add(`${pfx}-win-locked`);
|
|
158
|
+
} else {
|
|
159
|
+
el.classList.remove(`${pfx}-win-locked`);
|
|
160
|
+
}
|
|
161
|
+
const btn = el.querySelector(`.${pfx}-win-lock`);
|
|
162
|
+
if (btn) btn.title = locked ? 'Unlock window' : 'Lock window';
|
|
163
|
+
this.emit('window:lock', { id, locked });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_toggleLockButtons(show) {
|
|
167
|
+
const pfx = this._pfx;
|
|
168
|
+
for (const id in this._windows) {
|
|
169
|
+
const btn = this._windows[id].el.querySelector(`.${pfx}-win-lock`);
|
|
170
|
+
if (btn) btn.classList.toggle(`${pfx}-win-lock-hidden`, !show);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Focus / Layout ──
|
|
175
|
+
|
|
176
|
+
focus(id) {
|
|
177
|
+
const w = this._windows[id];
|
|
178
|
+
if (w) {
|
|
179
|
+
bringToFront(w.el);
|
|
180
|
+
this.emit('window:focus', { id, el: w.el });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
saveLayout(slot) {
|
|
185
|
+
const snap = {};
|
|
186
|
+
for (const [id, w] of Object.entries(this._windows)) {
|
|
187
|
+
const el = w.el;
|
|
188
|
+
snap[id] = {
|
|
189
|
+
left: el.style.left, top: el.style.top,
|
|
190
|
+
width: el.style.width, height: el.style.height,
|
|
191
|
+
zIndex: el.style.zIndex, locked: w.locked,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
this._layouts[slot] = snap;
|
|
195
|
+
this.emit('layout:save', { slot });
|
|
196
|
+
this._updateLayoutBar();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
restoreLayout(slot) {
|
|
200
|
+
const snap = this._layouts[slot];
|
|
201
|
+
if (!snap) return;
|
|
202
|
+
for (const [id, info] of Object.entries(snap)) {
|
|
203
|
+
const w = this._windows[id];
|
|
204
|
+
if (!w) continue;
|
|
205
|
+
Object.assign(w.el.style, { left: info.left, top: info.top, width: info.width });
|
|
206
|
+
if (info.height) w.el.style.height = info.height;
|
|
207
|
+
if (info.zIndex) w.el.style.zIndex = info.zIndex;
|
|
208
|
+
if (info.locked != null) this._setLocked(id, info.locked);
|
|
209
|
+
}
|
|
210
|
+
this.emit('layout:restore', { slot });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
hasLayout(slot) { return !!this._layouts[slot]; }
|
|
214
|
+
|
|
215
|
+
cycle(reverse = false) {
|
|
216
|
+
cycleWindow(this._container, reverse);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_sel(cls) { return `.${this._pfx}-${cls}`; }
|
|
220
|
+
|
|
221
|
+
// ── Layout Bar UI ──
|
|
222
|
+
|
|
223
|
+
_createLayoutBar() {
|
|
224
|
+
if (this._layoutBarEl || this._layoutSlots <= 0) return;
|
|
225
|
+
const pfx = this._pfx;
|
|
226
|
+
const bar = document.createElement('div');
|
|
227
|
+
bar.className = `${pfx}-layout-bar`;
|
|
228
|
+
this._container.appendChild(bar);
|
|
229
|
+
this._layoutBarEl = bar;
|
|
230
|
+
|
|
231
|
+
bar.addEventListener('click', (e) => {
|
|
232
|
+
const slot = e.target.closest('[data-layout-slot]');
|
|
233
|
+
if (!slot) return;
|
|
234
|
+
const num = parseInt(slot.dataset.layoutSlot);
|
|
235
|
+
if (e.ctrlKey || e.metaKey) {
|
|
236
|
+
this.saveLayout(num);
|
|
237
|
+
} else if (this._layouts[num]) {
|
|
238
|
+
this.restoreLayout(num);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
this._updateLayoutBar();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
_updateLayoutBar() {
|
|
246
|
+
if (!this._layoutBarEl) return;
|
|
247
|
+
const pfx = this._pfx;
|
|
248
|
+
let html = '';
|
|
249
|
+
for (let i = 1; i <= this._layoutSlots; i++) {
|
|
250
|
+
const saved = !!this._layouts[i];
|
|
251
|
+
const cls = saved ? `${pfx}-layout-slot ${pfx}-layout-saved` : `${pfx}-layout-slot`;
|
|
252
|
+
html += `<span class="${cls}" data-layout-slot="${i}">${i}</span>`;
|
|
253
|
+
}
|
|
254
|
+
this._layoutBarEl.innerHTML = html;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── Event binding ──
|
|
258
|
+
|
|
259
|
+
_isLocked(win) {
|
|
260
|
+
const id = win?.dataset?.winId;
|
|
261
|
+
return id ? !!this._windows[id]?.locked : false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
_bindEvents() {
|
|
265
|
+
if (this._bound) return;
|
|
266
|
+
this._bound = true;
|
|
267
|
+
const self = this;
|
|
268
|
+
const pfx = this._pfx;
|
|
269
|
+
|
|
270
|
+
document.addEventListener('mousedown', (e) => {
|
|
271
|
+
const anyWin = e.target.closest(`.${pfx}-win`);
|
|
272
|
+
if (anyWin) {
|
|
273
|
+
bringToFront(anyWin);
|
|
274
|
+
const winId = anyWin.dataset.winId;
|
|
275
|
+
if (winId) self.emit('window:focus', { id: winId, el: anyWin });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Lock button
|
|
279
|
+
const lockBtn = e.target.closest(`.${pfx}-win-lock`);
|
|
280
|
+
if (lockBtn) { self.toggleLock(lockBtn.dataset.lock); return; }
|
|
281
|
+
|
|
282
|
+
const closeBtn = e.target.closest(`.${pfx}-win-close`);
|
|
283
|
+
if (closeBtn) { self.closeWindow(closeBtn.dataset.close); return; }
|
|
284
|
+
|
|
285
|
+
const titlebar = e.target.closest(`.${pfx}-win-titlebar`);
|
|
286
|
+
if (titlebar) {
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
const win = titlebar.closest(`.${pfx}-win`);
|
|
289
|
+
bringToFront(win);
|
|
290
|
+
if (!self._isLocked(win)) {
|
|
291
|
+
self._dragState = { el: win, ox: e.clientX - win.offsetLeft, oy: e.clientY - win.offsetTop, origX: win.offsetLeft, origY: win.offsetTop };
|
|
292
|
+
win.classList.add(`${pfx}-win-dragging`);
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const rh = e.target.closest(`.${pfx}-win-resize`);
|
|
297
|
+
if (rh) {
|
|
298
|
+
e.preventDefault();
|
|
299
|
+
const win = rh.closest(`.${pfx}-win`);
|
|
300
|
+
bringToFront(win);
|
|
301
|
+
if (!self._isLocked(win)) {
|
|
302
|
+
self._resizeState = { el: win, startX: e.clientX, startY: e.clientY, startW: win.offsetWidth, startH: win.offsetHeight };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
document.addEventListener('mousemove', (e) => {
|
|
308
|
+
if (self._dragState) {
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
const d = self._dragState;
|
|
311
|
+
d.tx = e.clientX - d.ox - d.origX;
|
|
312
|
+
d.ty = e.clientY - d.oy - d.origY;
|
|
313
|
+
d.el.style.transform = `translate(${d.tx}px,${d.ty}px)`;
|
|
314
|
+
self.emit('window:move', { id: d.el.dataset.winId, el: d.el });
|
|
315
|
+
}
|
|
316
|
+
if (self._resizeState) {
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
const r = self._resizeState;
|
|
319
|
+
const minW = parseInt(r.el.dataset.minW) || 200;
|
|
320
|
+
const minH = parseInt(r.el.dataset.minH) || 60;
|
|
321
|
+
r.el.style.width = Math.max(minW, r.startW + e.clientX - r.startX) + 'px';
|
|
322
|
+
r.el.style.height = Math.max(minH, r.startH + e.clientY - r.startY) + 'px';
|
|
323
|
+
self.emit('window:resize', { id: r.el.dataset.winId, el: r.el });
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
document.addEventListener('mouseup', () => {
|
|
328
|
+
if (self._dragState) {
|
|
329
|
+
const d = self._dragState;
|
|
330
|
+
// Bake transform into left/top so offsetLeft/offsetTop stay correct
|
|
331
|
+
d.el.style.left = (d.origX + (d.tx || 0)) + 'px';
|
|
332
|
+
d.el.style.top = (d.origY + (d.ty || 0)) + 'px';
|
|
333
|
+
d.el.style.transform = '';
|
|
334
|
+
d.el.classList.remove(`${pfx}-win-dragging`);
|
|
335
|
+
self._dragState = null;
|
|
336
|
+
}
|
|
337
|
+
self._resizeState = null;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Touch
|
|
341
|
+
document.addEventListener('touchstart', (e) => {
|
|
342
|
+
const lockBtn = e.target.closest(`.${pfx}-win-lock`);
|
|
343
|
+
if (lockBtn) { self.toggleLock(lockBtn.dataset.lock); return; }
|
|
344
|
+
const closeBtn = e.target.closest(`.${pfx}-win-close`);
|
|
345
|
+
if (closeBtn) { self.closeWindow(closeBtn.dataset.close); return; }
|
|
346
|
+
const titlebar = e.target.closest(`.${pfx}-win-titlebar`);
|
|
347
|
+
if (titlebar) {
|
|
348
|
+
const win = titlebar.closest(`.${pfx}-win`);
|
|
349
|
+
const t = e.touches[0];
|
|
350
|
+
bringToFront(win);
|
|
351
|
+
if (!self._isLocked(win)) {
|
|
352
|
+
self._dragState = { el: win, ox: t.clientX - win.offsetLeft, oy: t.clientY - win.offsetTop, origX: win.offsetLeft, origY: win.offsetTop };
|
|
353
|
+
win.classList.add(`${pfx}-win-dragging`);
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const rh = e.target.closest(`.${pfx}-win-resize`);
|
|
358
|
+
if (rh) {
|
|
359
|
+
const win = rh.closest(`.${pfx}-win`);
|
|
360
|
+
const t = e.touches[0];
|
|
361
|
+
bringToFront(win);
|
|
362
|
+
if (!self._isLocked(win)) {
|
|
363
|
+
self._resizeState = { el: win, startX: t.clientX, startY: t.clientY, startW: win.offsetWidth, startH: win.offsetHeight };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}, { passive: true });
|
|
367
|
+
|
|
368
|
+
document.addEventListener('touchmove', (e) => {
|
|
369
|
+
if (self._dragState) {
|
|
370
|
+
e.preventDefault();
|
|
371
|
+
const t = e.touches[0];
|
|
372
|
+
const d = self._dragState;
|
|
373
|
+
d.tx = t.clientX - d.ox - d.origX;
|
|
374
|
+
d.ty = t.clientY - d.oy - d.origY;
|
|
375
|
+
d.el.style.transform = `translate(${d.tx}px,${d.ty}px)`;
|
|
376
|
+
self.emit('window:move', { id: d.el.dataset.winId, el: d.el });
|
|
377
|
+
}
|
|
378
|
+
if (self._resizeState) {
|
|
379
|
+
e.preventDefault();
|
|
380
|
+
const t = e.touches[0];
|
|
381
|
+
const r = self._resizeState;
|
|
382
|
+
r.el.style.width = Math.max(parseInt(r.el.dataset.minW) || 200, r.startW + t.clientX - r.startX) + 'px';
|
|
383
|
+
r.el.style.height = Math.max(parseInt(r.el.dataset.minH) || 60, r.startH + t.clientY - r.startY) + 'px';
|
|
384
|
+
self.emit('window:resize', { id: r.el.dataset.winId, el: r.el });
|
|
385
|
+
}
|
|
386
|
+
}, { passive: false });
|
|
387
|
+
|
|
388
|
+
document.addEventListener('touchend', () => {
|
|
389
|
+
if (self._dragState) {
|
|
390
|
+
const d = self._dragState;
|
|
391
|
+
d.el.style.left = (d.origX + (d.tx || 0)) + 'px';
|
|
392
|
+
d.el.style.top = (d.origY + (d.ty || 0)) + 'px';
|
|
393
|
+
d.el.style.transform = '';
|
|
394
|
+
d.el.classList.remove(`${pfx}-win-dragging`);
|
|
395
|
+
self._dragState = null;
|
|
396
|
+
}
|
|
397
|
+
self._resizeState = null;
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Keyboard shortcuts
|
|
401
|
+
document.addEventListener('keydown', (e) => {
|
|
402
|
+
if (e.key === 'Escape') {
|
|
403
|
+
if (self._escapeClose) self.closeAll();
|
|
404
|
+
self.emit('escape');
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (self._layoutSlots > 0) {
|
|
409
|
+
const num = parseInt(e.key);
|
|
410
|
+
if (num >= 1 && num <= self._layoutSlots) {
|
|
411
|
+
const isInput = e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable;
|
|
412
|
+
if (e.ctrlKey || e.metaKey) {
|
|
413
|
+
e.preventDefault();
|
|
414
|
+
self.saveLayout(num);
|
|
415
|
+
} else if (!isInput && !e.altKey) {
|
|
416
|
+
e.preventDefault();
|
|
417
|
+
if (self._layouts[num]) {
|
|
418
|
+
self.restoreLayout(num);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
package/src/z-index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Shared z-index counter for all window systems */
|
|
2
|
+
let nextZ = 100;
|
|
3
|
+
|
|
4
|
+
export function getNextZ() { return ++nextZ; }
|
|
5
|
+
|
|
6
|
+
export function bringToFront(el) {
|
|
7
|
+
if (el) el.style.zIndex = ++nextZ;
|
|
8
|
+
return nextZ;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Get all visible mt- windows sorted by z-index */
|
|
12
|
+
export function getAllWindows(container) {
|
|
13
|
+
const root = container || document;
|
|
14
|
+
return [...root.querySelectorAll('.mt-win')]
|
|
15
|
+
.filter(el => el.offsetParent !== null)
|
|
16
|
+
.sort((a, b) => (parseInt(a.style.zIndex) || 0) - (parseInt(b.style.zIndex) || 0));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Cycle to the next (or previous) window */
|
|
20
|
+
export function cycleWindow(container, reverse = false) {
|
|
21
|
+
const wins = getAllWindows(container);
|
|
22
|
+
if (wins.length < 2) return;
|
|
23
|
+
const top = wins[wins.length - 1];
|
|
24
|
+
let target;
|
|
25
|
+
if (reverse) {
|
|
26
|
+
target = wins[wins.length - 2];
|
|
27
|
+
} else {
|
|
28
|
+
top.style.zIndex = 1;
|
|
29
|
+
target = wins[0];
|
|
30
|
+
}
|
|
31
|
+
bringToFront(target);
|
|
32
|
+
const rect = target.getBoundingClientRect();
|
|
33
|
+
if (rect.top < 0) target.style.top = '10px';
|
|
34
|
+
if (rect.left < 0) target.style.left = '10px';
|
|
35
|
+
}
|