flexdesk 0.2.0 → 0.3.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/css/base.css +1484 -181
- package/css/flexdesk.css +1311 -18
- package/css/overrides.css +44 -0
- package/css/tokens.css +45 -0
- package/dist/charts.js +5 -3
- package/dist/charts.js.map +1 -1
- package/dist/{chunk-DVU44T77.js → chunk-ELXVW542.js} +196 -75
- package/dist/chunk-ELXVW542.js.map +7 -0
- package/dist/chunk-LH5TSOZW.js +1237 -0
- package/dist/chunk-LH5TSOZW.js.map +7 -0
- package/dist/{chunk-TLZUUFOE.js → chunk-O5OHMWBB.js} +10 -2
- package/dist/chunk-O5OHMWBB.js.map +7 -0
- package/dist/{chunk-CT4YXXLP.js → chunk-QIU5S2RU.js} +371 -73
- package/dist/chunk-QIU5S2RU.js.map +7 -0
- package/dist/chunk-QNQHQ24V.js +408 -0
- package/dist/chunk-QNQHQ24V.js.map +7 -0
- package/dist/{chunk-DRYCDMEG.js → chunk-XKDTIT4Q.js} +168 -12
- package/dist/chunk-XKDTIT4Q.js.map +7 -0
- package/dist/editor.js +3 -380
- package/dist/editor.js.map +3 -3
- package/dist/flexdesk.css +1311 -18
- package/dist/tiles.js +168 -41
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +45 -0
- package/dist/widgets.js +44 -14
- package/dist/widgets.js.map +2 -2
- package/dist/wm.js +2983 -142
- package/dist/wm.js.map +4 -4
- package/package.json +3 -2
- package/src/charts/chart_types.js +167 -0
- package/src/charts/plotly_wrapper.js +178 -10
- package/src/editor/notebook_tab_bar.js +39 -3
- package/src/tiles/tile_base.js +143 -35
- package/src/tiles/tile_grid.js +52 -1
- package/src/tiling/command_palette.js +71 -18
- package/src/tiling/desktops.js +36 -12
- package/src/tiling/keymap.js +24 -4
- package/src/tiling/shell.js +135 -24
- package/src/tiling/tab_strip.js +184 -0
- package/src/tiling/tile_breadcrumb.js +34 -2
- package/src/tiling/tile_renderer.js +1386 -21
- package/src/tiling/tile_tab_menu.js +101 -0
- package/src/tiling/tile_tree.js +82 -0
- package/src/tiling/wm.js +2352 -74
- package/src/ui/components/action_dropdown.js +34 -3
- package/src/ui/components/autocomplete_field.js +65 -13
- package/src/ui/components/context_menu.js +79 -8
- package/src/ui/components/data_table.js +508 -84
- package/src/ui/components/managed_window.js +928 -36
- package/src/ui/components/modal.js +214 -8
- package/dist/chunk-CT4YXXLP.js.map +0 -7
- package/dist/chunk-DRYCDMEG.js.map +0 -7
- package/dist/chunk-DVU44T77.js.map +0 -7
- package/dist/chunk-TLZUUFOE.js.map +0 -7
- package/dist/chunk-UCJ2WD4D.js +0 -625
- package/dist/chunk-UCJ2WD4D.js.map +0 -7
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/ui/components/action_dropdown.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * action_dropdown.js\n *\n * Reusable dropdown component for action buttons (Add Scenario, Add Widget, etc).\n * Handles opening, closing, auto-positioning, keyboard navigation, and outside clicks.\n *\n * Auto-positioning: measures available viewport space around the trigger element\n * and picks the best direction (above/below) and alignment (left/right).\n * Adds scrolling when the menu would exceed available vertical space.\n */\n\n/** Viewport margin in px \u2014 dropdown stays this far from edges. */\nconst EDGE_MARGIN = 8;\n/** Gap between trigger and dropdown in px. */\nconst GAP = 8;\n\n/**\n * ActionDropdown - A reusable dropdown component with smart auto-positioning.\n *\n * Usage:\n * ```javascript\n * const dropdown = new ActionDropdown({\n * trigger: buttonElement,\n * options: [\n * { type: 'option1', label: 'Option 1', icon: 'icon_name', description: 'Description' },\n * { type: 'option2', label: 'Option 2', icon: 'icon_name', description: 'Description' }\n * ],\n * onSelect: (option) => console.log('Selected:', option.type),\n * });\n *\n * // Control programmatically\n * dropdown.open();\n * dropdown.close();\n * dropdown.toggle();\n * dropdown.destroy();\n * ```\n */\nexport class ActionDropdown {\n /**\n * @param {Object} config\n * @param {HTMLElement} config.trigger - Button element that triggers the dropdown\n * @param {Array} config.options - Array of option objects with { type, label, icon, description? }\n * @param {Function} config.onSelect - Callback when an option is selected\n * @param {string} [config.className=''] - Additional CSS class for the dropdown\n * @param {string} [config.menuId] - Optional ID for the menu element\n */\n constructor(config) {\n this.trigger = config.trigger;\n this.options = config.options || [];\n this.onSelect = config.onSelect;\n this.className = config.className || '';\n this.menuId = config.menuId;\n\n this.isOpen = false;\n this.menuEl = null;\n\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);\n\n this._init();\n }\n\n /**\n * Initialize the dropdown.\n * @private\n */\n _init() {\n if (!this.trigger) {\n console.warn('[ActionDropdown] No trigger element provided');\n return;\n }\n\n this._createMenu();\n this.trigger.addEventListener('click', this._boundHandleTriggerClick);\n }\n\n /**\n * Create the dropdown menu element.\n * @private\n */\n _createMenu() {\n const menu = document.createElement('div');\n menu.className = `twm-action-dropdown-menu ${this.className}`.trim();\n menu.setAttribute('role', 'menu');\n menu.hidden = true;\n\n if (this.menuId) {\n menu.id = this.menuId;\n }\n\n this._renderOptions(menu);\n\n // Append to body so position:fixed works without clipping.\n document.body.appendChild(menu);\n\n this.menuEl = menu;\n }\n\n /**\n * Render option buttons into a container element.\n * @param {HTMLElement} container\n * @private\n */\n _renderOptions(container) {\n container.innerHTML = '';\n this.options.forEach((opt, index) => {\n const optionBtn = document.createElement('button');\n optionBtn.type = 'button';\n optionBtn.className = 'twm-action-dropdown-option';\n optionBtn.dataset.index = index;\n optionBtn.dataset.type = opt.type;\n optionBtn.setAttribute('role', 'menuitem');\n\n const iconHtml = opt.icon\n ? `<span class=\"material-symbols-outlined twm-option-icon\">${opt.icon}</span>`\n : '';\n\n const descHtml = opt.description\n ? `<span class=\"twm-option-desc\">${opt.description}</span>`\n : '';\n\n optionBtn.innerHTML = `\n ${iconHtml}\n <span class=\"option-text\">\n <span class=\"twm-option-label\">${opt.label}</span>\n ${descHtml}\n </span>\n `;\n\n optionBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this._selectOption(opt);\n });\n\n container.appendChild(optionBtn);\n });\n }\n\n /**\n * Handle trigger button click.\n * @param {MouseEvent} e\n * @private\n */\n _handleTriggerClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.toggle();\n }\n\n /**\n * Toggle the dropdown open/closed.\n * @param {boolean} [forceState] - Optional forced state\n */\n toggle(forceState) {\n const shouldOpen = typeof forceState === 'boolean' ? forceState : !this.isOpen;\n if (shouldOpen) {\n this.open();\n } else {\n this.close();\n }\n }\n\n /**\n * Open the dropdown with auto-positioning.\n */\n open() {\n if (this.isOpen || !this.menuEl) return;\n\n this.isOpen = true;\n this.menuEl.hidden = false;\n\n // Position against trigger\n ActionDropdown.position(this.trigger, this.menuEl);\n\n // Animate in\n requestAnimationFrame(() => {\n this.menuEl.classList.add('visible');\n });\n\n // Update trigger state\n this.trigger?.classList.add('twm-is-open');\n\n // Add document listeners\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n document.addEventListener('keydown', this._boundHandleKeydown);\n\n // Focus first option for keyboard accessibility\n const firstOption = this.menuEl.querySelector('.twm-action-dropdown-option');\n firstOption?.focus();\n }\n\n /**\n * Close the dropdown.\n */\n close() {\n if (!this.isOpen) return;\n\n this.isOpen = false;\n\n if (this.menuEl) {\n this.menuEl.classList.remove('visible');\n this.menuEl.hidden = true;\n }\n\n // Update trigger state\n this.trigger?.classList.remove('twm-is-open');\n\n // Remove document listeners\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n /**\n * Handle document click (close on outside click).\n * @param {MouseEvent} e\n * @private\n */\n _handleDocumentClick(e) {\n if (!this.isOpen) return;\n\n // Don't close if clicking inside dropdown or trigger\n if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {\n return;\n }\n\n this.close();\n }\n\n /**\n * Handle keyboard events.\n * @param {KeyboardEvent} e\n * @private\n */\n _handleKeydown(e) {\n if (e.key === 'Escape') {\n this.close();\n this.trigger?.focus();\n return;\n }\n\n // Arrow key navigation\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n this._navigateOptions(e.key === 'ArrowDown' ? 1 : -1);\n }\n }\n\n /**\n * Navigate options with arrow keys.\n * @param {number} direction - 1 for down, -1 for up\n * @private\n */\n _navigateOptions(direction) {\n const options = this.menuEl?.querySelectorAll('.twm-action-dropdown-option');\n if (!options || options.length === 0) return;\n\n const currentIndex = Array.from(options).findIndex(opt => opt === document.activeElement);\n let nextIndex = currentIndex + direction;\n\n if (nextIndex < 0) nextIndex = options.length - 1;\n if (nextIndex >= options.length) nextIndex = 0;\n\n options[nextIndex]?.focus();\n }\n\n /**\n * Handle option selection.\n * @param {Object} option - The selected option\n * @private\n */\n _selectOption(option) {\n this.close();\n\n if (this.onSelect) {\n this.onSelect(option);\n }\n }\n\n /**\n * Update the options dynamically.\n * @param {Array} newOptions - New options array\n */\n setOptions(newOptions) {\n this.options = newOptions;\n if (this.menuEl) {\n this._renderOptions(this.menuEl);\n }\n }\n\n /**\n * Destroy the dropdown and clean up.\n */\n destroy() {\n this.close();\n\n // Remove trigger listener\n this.trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n\n // Remove menu element\n this.menuEl?.remove();\n this.menuEl = null;\n\n this.trigger = null;\n this.options = [];\n this.onSelect = null;\n }\n\n // =========================================================================\n // STATIC \u2014 shared auto-positioning for any trigger + menu pair\n // =========================================================================\n\n /**\n * Position a dropdown menu relative to a trigger element.\n * Uses `position: fixed` to avoid overflow clipping.\n * Picks above/below based on available space, aligns left/right edge,\n * and applies `max-height` + `overflow-y: auto` when the menu is taller\n * than the available space.\n *\n * @param {HTMLElement} trigger - The element the dropdown opens from\n * @param {HTMLElement} menu - The dropdown menu element to position\n */\n static position(trigger, menu) {\n if (!trigger || !menu) return;\n\n // Reset any prior inline position so natural size can be measured.\n Object.assign(menu.style, {\n position: 'fixed',\n top: 'auto',\n bottom: 'auto',\n left: 'auto',\n right: 'auto',\n maxHeight: '',\n overflowY: ''\n });\n\n const triggerRect = trigger.getBoundingClientRect();\n const menuRect = menu.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // \u2500\u2500 Vertical: prefer below, flip above if insufficient room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;\n const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;\n\n let top;\n let maxHeight;\n\n if (menuRect.height <= spaceBelow) {\n // Fits below \u2014 use natural height.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else if (menuRect.height <= spaceAbove) {\n // Fits above \u2014 use natural height.\n top = triggerRect.top - GAP - menuRect.height;\n maxHeight = spaceAbove;\n } else if (spaceBelow >= spaceAbove) {\n // More space below \u2014 scroll.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else {\n // More space above \u2014 scroll.\n maxHeight = spaceAbove;\n top = EDGE_MARGIN;\n }\n\n // \u2500\u2500 Horizontal: align left-edge with trigger, flip if off-screen \u2500\u2500\n let left = triggerRect.left;\n\n if (left + menuRect.width > vw - EDGE_MARGIN) {\n // Align right edges instead.\n left = triggerRect.right - menuRect.width;\n }\n\n // Still off-screen? Clamp to edges.\n left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));\n\n Object.assign(menu.style, {\n position: 'fixed',\n top: `${Math.round(top)}px`,\n left: `${Math.round(left)}px`,\n maxHeight: `${Math.round(maxHeight)}px`,\n overflowY: 'auto'\n });\n }\n}\n\nexport default ActionDropdown;\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
package/dist/chunk-UCJ2WD4D.js
DELETED
|
@@ -1,625 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getSetting
|
|
3
|
-
} from "./chunk-FL5KFNQH.js";
|
|
4
|
-
|
|
5
|
-
// src/ui/components/managed_window.js
|
|
6
|
-
var STORAGE_KEY = "ecosim.managedWindows.v1";
|
|
7
|
-
var BASE_Z_INDEX = 6e3;
|
|
8
|
-
var MAX_Z_INDEX = 6999;
|
|
9
|
-
var TOP_BAR_HEIGHT = 35;
|
|
10
|
-
var BOTTOM_BAR_HEIGHT = 22;
|
|
11
|
-
var DEFAULT_ICON = "web_asset";
|
|
12
|
-
var _zIndexCounter = 0;
|
|
13
|
-
var _activeWindows = /* @__PURE__ */ new Map();
|
|
14
|
-
function _collectFocusable(root) {
|
|
15
|
-
if (!root) return [];
|
|
16
|
-
const sel = [
|
|
17
|
-
"a[href]",
|
|
18
|
-
"button:not([disabled])",
|
|
19
|
-
'input:not([disabled]):not([type="hidden"])',
|
|
20
|
-
"select:not([disabled])",
|
|
21
|
-
"textarea:not([disabled])",
|
|
22
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
23
|
-
].join(",");
|
|
24
|
-
return Array.from(root.querySelectorAll(sel)).filter((el) => {
|
|
25
|
-
if (el.hidden) return false;
|
|
26
|
-
if (el.closest("[hidden]")) return false;
|
|
27
|
-
const r = el.getBoundingClientRect();
|
|
28
|
-
return r.width > 0 || r.height > 0;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
var _stateCache = null;
|
|
32
|
-
function _loadState() {
|
|
33
|
-
if (_stateCache) return _stateCache;
|
|
34
|
-
try {
|
|
35
|
-
const raw = localStorage.getItem(STORAGE_KEY);
|
|
36
|
-
_stateCache = raw ? JSON.parse(raw) : {};
|
|
37
|
-
} catch {
|
|
38
|
-
_stateCache = {};
|
|
39
|
-
}
|
|
40
|
-
return _stateCache;
|
|
41
|
-
}
|
|
42
|
-
function _saveState(state) {
|
|
43
|
-
_stateCache = state;
|
|
44
|
-
try {
|
|
45
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
46
|
-
} catch (err) {
|
|
47
|
-
console.warn("[ManagedWindow] Failed to save state:", err);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function _getWindowState(id) {
|
|
51
|
-
const state = _loadState();
|
|
52
|
-
return state[id] || null;
|
|
53
|
-
}
|
|
54
|
-
function _setWindowState(id, windowState) {
|
|
55
|
-
const state = _loadState();
|
|
56
|
-
state[id] = windowState;
|
|
57
|
-
_saveState(state);
|
|
58
|
-
}
|
|
59
|
-
var ManagedWindow = class {
|
|
60
|
-
/**
|
|
61
|
-
* @param {Object} options
|
|
62
|
-
* @param {string} options.id - Unique window ID for persistence
|
|
63
|
-
* @param {string} options.title - Window title
|
|
64
|
-
* @param {string} [options.icon] - Material Symbols icon name (default: 'web_asset')
|
|
65
|
-
* @param {HTMLElement|Function} options.content - Content element or render function
|
|
66
|
-
* @param {number} [options.minWidth=400] - Minimum width
|
|
67
|
-
* @param {number} [options.minHeight=300] - Minimum height
|
|
68
|
-
* @param {number} [options.defaultWidth=600] - Default width
|
|
69
|
-
* @param {number} [options.defaultHeight=400] - Default height
|
|
70
|
-
* @param {boolean} [options.canMinimize=true] - Whether minimize button is shown
|
|
71
|
-
* @param {boolean} [options.canMaximize=true] - Whether maximize button is shown
|
|
72
|
-
* @param {boolean} [options.canResize=true] - Whether window can be resized
|
|
73
|
-
* @param {boolean} [options.canDrag=true] - Whether window can be dragged
|
|
74
|
-
* @param {boolean} [options.modal=false] - Whether to show backdrop
|
|
75
|
-
* @param {Function} [options.onClose] - Callback when window is closed
|
|
76
|
-
* @param {Function} [options.beforeClose] - Guard called before close. Return false (or a Promise resolving to false) to prevent closing.
|
|
77
|
-
* @param {Function} [options.onMinimize] - Callback when window is minimized
|
|
78
|
-
*/
|
|
79
|
-
constructor(options) {
|
|
80
|
-
this.id = options.id;
|
|
81
|
-
this.title = options.title || "Window";
|
|
82
|
-
this.icon = options.icon || DEFAULT_ICON;
|
|
83
|
-
this.content = options.content;
|
|
84
|
-
this.minWidth = options.minWidth || 400;
|
|
85
|
-
this.minHeight = options.minHeight || 300;
|
|
86
|
-
this.defaultWidth = options.defaultWidth || 600;
|
|
87
|
-
this.defaultHeight = options.defaultHeight || 400;
|
|
88
|
-
this.canMinimize = options.canMinimize ?? true;
|
|
89
|
-
this.canMaximize = options.canMaximize ?? true;
|
|
90
|
-
this.canResize = options.canResize ?? true;
|
|
91
|
-
this.modal = options.modal ?? false;
|
|
92
|
-
this.backdropBlur = options.backdropBlur;
|
|
93
|
-
this.backdropOpacity = options.backdropOpacity;
|
|
94
|
-
this.canDrag = options.canDrag ?? !this.modal;
|
|
95
|
-
this.onClose = options.onClose;
|
|
96
|
-
this.beforeClose = options.beforeClose || null;
|
|
97
|
-
this.onMinimize = options.onMinimize;
|
|
98
|
-
this.element = null;
|
|
99
|
-
this.backdropElement = null;
|
|
100
|
-
this.contentContainer = null;
|
|
101
|
-
this.isVisible = false;
|
|
102
|
-
this.isMinimized = false;
|
|
103
|
-
this.isMaximized = false;
|
|
104
|
-
this.zIndex = BASE_Z_INDEX;
|
|
105
|
-
this.x = 0;
|
|
106
|
-
this.y = 0;
|
|
107
|
-
const maxWidth = window.innerWidth;
|
|
108
|
-
const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;
|
|
109
|
-
this.width = Math.min(this.defaultWidth, maxWidth);
|
|
110
|
-
this.height = Math.min(this.defaultHeight, maxHeight);
|
|
111
|
-
this._preMaximizeState = null;
|
|
112
|
-
this._dragState = null;
|
|
113
|
-
this._resizeState = null;
|
|
114
|
-
this._boundOnPointerMove = this._onPointerMove.bind(this);
|
|
115
|
-
this._boundOnPointerUp = this._onPointerUp.bind(this);
|
|
116
|
-
this._boundOnKeyDown = this._onKeyDown.bind(this);
|
|
117
|
-
_activeWindows.set(this.id, this);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Show the window.
|
|
121
|
-
*/
|
|
122
|
-
show() {
|
|
123
|
-
if (this.isVisible && !this.isMinimized) {
|
|
124
|
-
this.bringToFront();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (!this.element) {
|
|
128
|
-
this._build();
|
|
129
|
-
this._restoreState();
|
|
130
|
-
}
|
|
131
|
-
if (this.isMinimized) {
|
|
132
|
-
this._restore();
|
|
133
|
-
} else {
|
|
134
|
-
this._applyPosition();
|
|
135
|
-
document.body.appendChild(this.element);
|
|
136
|
-
if (this.modal && this.backdropElement) {
|
|
137
|
-
document.body.appendChild(this.backdropElement);
|
|
138
|
-
}
|
|
139
|
-
this.isVisible = true;
|
|
140
|
-
}
|
|
141
|
-
this.bringToFront();
|
|
142
|
-
document.addEventListener("keydown", this._boundOnKeyDown);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Hide/close the window.
|
|
146
|
-
* @param {{ force?: boolean }} [options] - Pass force:true to bypass the beforeClose guard.
|
|
147
|
-
*/
|
|
148
|
-
close({ force = false } = {}) {
|
|
149
|
-
if (!this.isVisible) return;
|
|
150
|
-
if (!force && this.beforeClose) {
|
|
151
|
-
const result = this.beforeClose();
|
|
152
|
-
if (result && typeof result.then === "function") {
|
|
153
|
-
result.then((allowed) => {
|
|
154
|
-
if (allowed !== false) this._doClose();
|
|
155
|
-
});
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (result === false) return;
|
|
159
|
-
}
|
|
160
|
-
this._doClose();
|
|
161
|
-
}
|
|
162
|
-
/** Internal close — always executes, no guard. */
|
|
163
|
-
_doClose() {
|
|
164
|
-
if (!this.isVisible) return;
|
|
165
|
-
this._saveCurrentState();
|
|
166
|
-
if (this.onClose) {
|
|
167
|
-
this.onClose();
|
|
168
|
-
}
|
|
169
|
-
if (this.element && this.element.parentNode) {
|
|
170
|
-
this.element.parentNode.removeChild(this.element);
|
|
171
|
-
}
|
|
172
|
-
if (this.backdropElement && this.backdropElement.parentNode) {
|
|
173
|
-
this.backdropElement.parentNode.removeChild(this.backdropElement);
|
|
174
|
-
}
|
|
175
|
-
this.isVisible = false;
|
|
176
|
-
this.isMinimized = false;
|
|
177
|
-
document.removeEventListener("keydown", this._boundOnKeyDown);
|
|
178
|
-
window.dispatchEvent(new CustomEvent("managed-window-closed", { detail: { id: this.id } }));
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Minimize to taskbar with animation.
|
|
182
|
-
*/
|
|
183
|
-
minimize() {
|
|
184
|
-
if (!this.isVisible || this.isMinimized) return;
|
|
185
|
-
this._saveCurrentState();
|
|
186
|
-
this.isMinimized = true;
|
|
187
|
-
if (this.onMinimize) {
|
|
188
|
-
this.onMinimize();
|
|
189
|
-
}
|
|
190
|
-
if (this.backdropElement) {
|
|
191
|
-
this.backdropElement.style.display = "none";
|
|
192
|
-
}
|
|
193
|
-
window.dispatchEvent(new CustomEvent("managed-window-minimized", {
|
|
194
|
-
detail: { id: this.id, title: this.title, icon: this.icon }
|
|
195
|
-
}));
|
|
196
|
-
if (!this.element) return;
|
|
197
|
-
if (!getSetting("window.animateMinimize", true)) {
|
|
198
|
-
this.element.style.display = "none";
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
this._setMinimizeTargetProperties();
|
|
202
|
-
this.element.classList.add("twm-managed-window--minimizing");
|
|
203
|
-
setTimeout(() => {
|
|
204
|
-
if (this.element) {
|
|
205
|
-
this.element.style.display = "none";
|
|
206
|
-
this.element.classList.remove("twm-managed-window--minimizing");
|
|
207
|
-
this._clearTargetProperties();
|
|
208
|
-
}
|
|
209
|
-
}, 200);
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Restore from minimized state with animation.
|
|
213
|
-
*/
|
|
214
|
-
_restore() {
|
|
215
|
-
if (!this.isMinimized) return;
|
|
216
|
-
const taskbarRect = this._getTaskbarItemRect();
|
|
217
|
-
if (this.element) {
|
|
218
|
-
this.element.style.display = "";
|
|
219
|
-
const animate = getSetting("window.animateMinimize", true);
|
|
220
|
-
if (animate) {
|
|
221
|
-
this._setRestoreTargetProperties(taskbarRect);
|
|
222
|
-
this.element.classList.add("twm-managed-window--restoring");
|
|
223
|
-
setTimeout(() => {
|
|
224
|
-
if (this.element) {
|
|
225
|
-
this.element.classList.remove("twm-managed-window--restoring");
|
|
226
|
-
this._clearTargetProperties();
|
|
227
|
-
}
|
|
228
|
-
}, 250);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (this.modal && this.backdropElement) {
|
|
232
|
-
this.backdropElement.style.display = "";
|
|
233
|
-
}
|
|
234
|
-
this.isMinimized = false;
|
|
235
|
-
this.bringToFront();
|
|
236
|
-
window.dispatchEvent(new CustomEvent("managed-window-restored", { detail: { id: this.id } }));
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Toggle maximize state.
|
|
240
|
-
*/
|
|
241
|
-
toggleMaximize() {
|
|
242
|
-
if (!this.canMaximize) return;
|
|
243
|
-
if (this.isMaximized) {
|
|
244
|
-
if (this._preMaximizeState) {
|
|
245
|
-
this.x = this._preMaximizeState.x;
|
|
246
|
-
this.y = this._preMaximizeState.y;
|
|
247
|
-
this.width = this._preMaximizeState.width;
|
|
248
|
-
this.height = this._preMaximizeState.height;
|
|
249
|
-
this._preMaximizeState = null;
|
|
250
|
-
}
|
|
251
|
-
this.isMaximized = false;
|
|
252
|
-
} else {
|
|
253
|
-
this._preMaximizeState = { x: this.x, y: this.y, width: this.width, height: this.height };
|
|
254
|
-
this.x = 0;
|
|
255
|
-
this.y = TOP_BAR_HEIGHT;
|
|
256
|
-
this.width = window.innerWidth;
|
|
257
|
-
this.height = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;
|
|
258
|
-
this.isMaximized = true;
|
|
259
|
-
}
|
|
260
|
-
this._applyPosition();
|
|
261
|
-
this._saveCurrentState();
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Find the taskbar button for this window.
|
|
265
|
-
* @returns {DOMRect|null}
|
|
266
|
-
*/
|
|
267
|
-
_getTaskbarItemRect() {
|
|
268
|
-
const btn = document.querySelector(`.twm-bar-windows__item[data-window-id="${this.id}"]`);
|
|
269
|
-
return btn ? btn.getBoundingClientRect() : null;
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Set CSS custom properties to animate minimize toward the taskbar item.
|
|
273
|
-
*/
|
|
274
|
-
_setMinimizeTargetProperties() {
|
|
275
|
-
const targetRect = this._getTaskbarItemRect();
|
|
276
|
-
if (!targetRect || !this.element) return;
|
|
277
|
-
const winRect = this.element.getBoundingClientRect();
|
|
278
|
-
const winCenterX = winRect.left + winRect.width / 2;
|
|
279
|
-
const winCenterY = winRect.top + winRect.height / 2;
|
|
280
|
-
const targetCenterX = targetRect.left + targetRect.width / 2;
|
|
281
|
-
const targetCenterY = targetRect.top + targetRect.height / 2;
|
|
282
|
-
const dx = targetCenterX - winCenterX;
|
|
283
|
-
const dy = targetCenterY - winCenterY;
|
|
284
|
-
const scale = Math.min(targetRect.width / winRect.width, targetRect.height / winRect.height, 0.15);
|
|
285
|
-
this.element.style.setProperty("--mw-target-x", `${dx}px`);
|
|
286
|
-
this.element.style.setProperty("--mw-target-y", `${dy}px`);
|
|
287
|
-
this.element.style.setProperty("--mw-target-scale", scale);
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Set CSS custom properties to animate restore from the taskbar item position.
|
|
291
|
-
* @param {DOMRect|null} taskbarRect
|
|
292
|
-
*/
|
|
293
|
-
_setRestoreTargetProperties(taskbarRect) {
|
|
294
|
-
if (!taskbarRect || !this.element) return;
|
|
295
|
-
const winRect = this.element.getBoundingClientRect();
|
|
296
|
-
const winCenterX = winRect.left + winRect.width / 2;
|
|
297
|
-
const winCenterY = winRect.top + winRect.height / 2;
|
|
298
|
-
const targetCenterX = taskbarRect.left + taskbarRect.width / 2;
|
|
299
|
-
const targetCenterY = taskbarRect.top + taskbarRect.height / 2;
|
|
300
|
-
const dx = targetCenterX - winCenterX;
|
|
301
|
-
const dy = targetCenterY - winCenterY;
|
|
302
|
-
const scale = Math.min(taskbarRect.width / winRect.width, taskbarRect.height / winRect.height, 0.15);
|
|
303
|
-
this.element.style.setProperty("--mw-target-x", `${dx}px`);
|
|
304
|
-
this.element.style.setProperty("--mw-target-y", `${dy}px`);
|
|
305
|
-
this.element.style.setProperty("--mw-target-scale", scale);
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Clear animation CSS custom properties.
|
|
309
|
-
*/
|
|
310
|
-
_clearTargetProperties() {
|
|
311
|
-
if (!this.element) return;
|
|
312
|
-
this.element.style.removeProperty("--mw-target-x");
|
|
313
|
-
this.element.style.removeProperty("--mw-target-y");
|
|
314
|
-
this.element.style.removeProperty("--mw-target-scale");
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Bring window to front of z-order.
|
|
318
|
-
*/
|
|
319
|
-
bringToFront() {
|
|
320
|
-
_zIndexCounter++;
|
|
321
|
-
this.zIndex = Math.min(BASE_Z_INDEX + _zIndexCounter, MAX_Z_INDEX);
|
|
322
|
-
if (this.element) {
|
|
323
|
-
this.element.style.zIndex = this.zIndex.toString();
|
|
324
|
-
}
|
|
325
|
-
if (this.backdropElement) {
|
|
326
|
-
this.backdropElement.style.zIndex = (this.zIndex - 1).toString();
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Update window title.
|
|
331
|
-
*/
|
|
332
|
-
setTitle(title) {
|
|
333
|
-
this.title = title;
|
|
334
|
-
if (this.element) {
|
|
335
|
-
const titleEl = this.element.querySelector(".twm-managed-window__title");
|
|
336
|
-
if (titleEl) titleEl.textContent = title;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
// ========== Private Methods ==========
|
|
340
|
-
_build() {
|
|
341
|
-
if (this.modal) {
|
|
342
|
-
this.backdropElement = document.createElement("div");
|
|
343
|
-
this.backdropElement.className = "twm-managed-window__backdrop";
|
|
344
|
-
if (this.backdropBlur !== void 0 && this.backdropBlur !== null) {
|
|
345
|
-
this.backdropElement.style.backdropFilter = this.backdropBlur > 0 ? `blur(${this.backdropBlur}px)` : "none";
|
|
346
|
-
}
|
|
347
|
-
if (this.backdropOpacity !== void 0 && this.backdropOpacity !== null) {
|
|
348
|
-
this.backdropElement.style.background = `rgba(0, 0, 0, ${this.backdropOpacity})`;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
this.element = document.createElement("div");
|
|
352
|
-
this.element.className = "twm-managed-window";
|
|
353
|
-
if (!this.canDrag) {
|
|
354
|
-
this.element.classList.add("twm-managed-window--no-drag");
|
|
355
|
-
}
|
|
356
|
-
if (this.modal) {
|
|
357
|
-
this.element.classList.add("twm-managed-window--modal");
|
|
358
|
-
}
|
|
359
|
-
this.element.setAttribute("data-window-id", this.id);
|
|
360
|
-
const topbar = document.createElement("div");
|
|
361
|
-
topbar.className = "twm-managed-window__topbar";
|
|
362
|
-
const icon = document.createElement("span");
|
|
363
|
-
icon.className = "twm-managed-window__icon material-symbols-outlined";
|
|
364
|
-
icon.textContent = this.icon;
|
|
365
|
-
const title = document.createElement("div");
|
|
366
|
-
title.className = "twm-managed-window__title";
|
|
367
|
-
title.textContent = this.title;
|
|
368
|
-
const buttons = document.createElement("div");
|
|
369
|
-
buttons.className = "twm-managed-window__buttons";
|
|
370
|
-
if (this.canMinimize) {
|
|
371
|
-
const minBtn = document.createElement("button");
|
|
372
|
-
minBtn.className = "twm-managed-window__btn managed-window__btn--minimize";
|
|
373
|
-
minBtn.type = "button";
|
|
374
|
-
minBtn.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10"><path d="M1 5h8" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>';
|
|
375
|
-
minBtn.title = "Minimize";
|
|
376
|
-
minBtn.addEventListener("click", (e) => {
|
|
377
|
-
e.stopPropagation();
|
|
378
|
-
this.minimize();
|
|
379
|
-
});
|
|
380
|
-
buttons.appendChild(minBtn);
|
|
381
|
-
}
|
|
382
|
-
if (this.canMaximize) {
|
|
383
|
-
const maxBtn = document.createElement("button");
|
|
384
|
-
maxBtn.className = "twm-managed-window__btn managed-window__btn--maximize";
|
|
385
|
-
maxBtn.type = "button";
|
|
386
|
-
maxBtn.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10"><rect x="1" y="1" width="8" height="8" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>';
|
|
387
|
-
maxBtn.title = "Maximize";
|
|
388
|
-
maxBtn.addEventListener("click", (e) => {
|
|
389
|
-
e.stopPropagation();
|
|
390
|
-
this.toggleMaximize();
|
|
391
|
-
});
|
|
392
|
-
buttons.appendChild(maxBtn);
|
|
393
|
-
}
|
|
394
|
-
const closeBtn = document.createElement("button");
|
|
395
|
-
closeBtn.className = "twm-managed-window__btn twm-managed-window__btn--close";
|
|
396
|
-
closeBtn.type = "button";
|
|
397
|
-
closeBtn.innerHTML = '<svg width="10" height="10" viewBox="0 0 10 10"><path d="M1 1l8 8M9 1l-8 8" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>';
|
|
398
|
-
closeBtn.title = "Close";
|
|
399
|
-
closeBtn.addEventListener("click", (e) => {
|
|
400
|
-
e.stopPropagation();
|
|
401
|
-
this.close();
|
|
402
|
-
});
|
|
403
|
-
buttons.appendChild(closeBtn);
|
|
404
|
-
topbar.appendChild(icon);
|
|
405
|
-
topbar.appendChild(title);
|
|
406
|
-
topbar.appendChild(buttons);
|
|
407
|
-
this.contentContainer = document.createElement("div");
|
|
408
|
-
this.contentContainer.className = "twm-managed-window__content";
|
|
409
|
-
if (this.content instanceof HTMLElement) {
|
|
410
|
-
this.contentContainer.appendChild(this.content);
|
|
411
|
-
} else if (typeof this.content === "function") {
|
|
412
|
-
const rendered = this.content();
|
|
413
|
-
if (rendered instanceof HTMLElement) {
|
|
414
|
-
this.contentContainer.appendChild(rendered);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
this.element.appendChild(topbar);
|
|
418
|
-
this.element.appendChild(this.contentContainer);
|
|
419
|
-
if (this.canResize) {
|
|
420
|
-
this._addResizeHandles();
|
|
421
|
-
}
|
|
422
|
-
topbar.addEventListener("pointerdown", (e) => this._onTopbarPointerDown(e));
|
|
423
|
-
if (this.canMaximize) {
|
|
424
|
-
topbar.addEventListener("dblclick", () => this.toggleMaximize());
|
|
425
|
-
}
|
|
426
|
-
this.element.addEventListener("pointerdown", () => this.bringToFront());
|
|
427
|
-
}
|
|
428
|
-
_addResizeHandles() {
|
|
429
|
-
const directions = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
430
|
-
for (const dir of directions) {
|
|
431
|
-
const handle = document.createElement("div");
|
|
432
|
-
handle.className = `twm-managed-window__resize managed-window__resize--${dir}`;
|
|
433
|
-
handle.addEventListener("pointerdown", (e) => this._onResizePointerDown(e, dir));
|
|
434
|
-
this.element.appendChild(handle);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
_applyPosition() {
|
|
438
|
-
if (!this.element) return;
|
|
439
|
-
const maxWidth = window.innerWidth;
|
|
440
|
-
const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;
|
|
441
|
-
this.width = Math.max(this.minWidth, Math.min(this.width, maxWidth));
|
|
442
|
-
this.height = Math.max(this.minHeight, Math.min(this.height, maxHeight));
|
|
443
|
-
const maxX = Math.max(0, maxWidth - this.width);
|
|
444
|
-
const maxY = Math.max(TOP_BAR_HEIGHT, window.innerHeight - this.height - BOTTOM_BAR_HEIGHT);
|
|
445
|
-
this.x = Math.max(0, Math.min(this.x, maxX));
|
|
446
|
-
this.y = Math.max(TOP_BAR_HEIGHT, Math.min(this.y, maxY));
|
|
447
|
-
this.element.style.left = `${this.x}px`;
|
|
448
|
-
this.element.style.top = `${this.y}px`;
|
|
449
|
-
this.element.style.width = `${this.width}px`;
|
|
450
|
-
this.element.style.height = `${this.height}px`;
|
|
451
|
-
}
|
|
452
|
-
_restoreState() {
|
|
453
|
-
const maxWidth = window.innerWidth;
|
|
454
|
-
const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;
|
|
455
|
-
const saved = this.modal ? null : _getWindowState(this.id);
|
|
456
|
-
if (saved) {
|
|
457
|
-
this.x = saved.x ?? this.x;
|
|
458
|
-
this.y = saved.y ?? this.y;
|
|
459
|
-
this.width = Math.min(saved.width ?? this.width, maxWidth);
|
|
460
|
-
this.height = Math.min(saved.height ?? this.height, maxHeight);
|
|
461
|
-
this.isMaximized = saved.maximized ?? false;
|
|
462
|
-
if (this.isMaximized && this.canMaximize) {
|
|
463
|
-
this._preMaximizeState = { x: saved.x, y: saved.y, width: saved.width, height: saved.height };
|
|
464
|
-
this.x = 0;
|
|
465
|
-
this.y = TOP_BAR_HEIGHT;
|
|
466
|
-
this.width = maxWidth;
|
|
467
|
-
this.height = maxHeight;
|
|
468
|
-
}
|
|
469
|
-
} else {
|
|
470
|
-
this.x = Math.max(0, (maxWidth - this.width) / 2);
|
|
471
|
-
this.y = Math.max(TOP_BAR_HEIGHT, TOP_BAR_HEIGHT + (maxHeight - this.height) / 2);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
_saveCurrentState() {
|
|
475
|
-
if (this.modal) return;
|
|
476
|
-
_setWindowState(this.id, {
|
|
477
|
-
x: this._preMaximizeState?.x ?? this.x,
|
|
478
|
-
y: this._preMaximizeState?.y ?? this.y,
|
|
479
|
-
width: this._preMaximizeState?.width ?? this.width,
|
|
480
|
-
height: this._preMaximizeState?.height ?? this.height,
|
|
481
|
-
maximized: this.isMaximized
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
// ========== Drag Handling ==========
|
|
485
|
-
_onTopbarPointerDown(e) {
|
|
486
|
-
if (e.target.closest(".twm-managed-window__buttons")) return;
|
|
487
|
-
if (this.isMaximized) return;
|
|
488
|
-
if (!this.canDrag) return;
|
|
489
|
-
e.preventDefault();
|
|
490
|
-
this._dragState = {
|
|
491
|
-
startX: e.clientX,
|
|
492
|
-
startY: e.clientY,
|
|
493
|
-
startWinX: this.x,
|
|
494
|
-
startWinY: this.y
|
|
495
|
-
};
|
|
496
|
-
document.addEventListener("pointermove", this._boundOnPointerMove);
|
|
497
|
-
document.addEventListener("pointerup", this._boundOnPointerUp);
|
|
498
|
-
}
|
|
499
|
-
_onPointerMove(e) {
|
|
500
|
-
if (this._dragState) {
|
|
501
|
-
const dx = e.clientX - this._dragState.startX;
|
|
502
|
-
const dy = e.clientY - this._dragState.startY;
|
|
503
|
-
this.x = this._dragState.startWinX + dx;
|
|
504
|
-
this.y = this._dragState.startWinY + dy;
|
|
505
|
-
this._applyPosition();
|
|
506
|
-
} else if (this._resizeState) {
|
|
507
|
-
this._handleResize(e);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
_onPointerUp() {
|
|
511
|
-
if (this._dragState || this._resizeState) {
|
|
512
|
-
this._saveCurrentState();
|
|
513
|
-
}
|
|
514
|
-
this._dragState = null;
|
|
515
|
-
this._resizeState = null;
|
|
516
|
-
document.removeEventListener("pointermove", this._boundOnPointerMove);
|
|
517
|
-
document.removeEventListener("pointerup", this._boundOnPointerUp);
|
|
518
|
-
}
|
|
519
|
-
// ========== Resize Handling ==========
|
|
520
|
-
_onResizePointerDown(e, direction) {
|
|
521
|
-
if (this.isMaximized) return;
|
|
522
|
-
e.preventDefault();
|
|
523
|
-
e.stopPropagation();
|
|
524
|
-
this._resizeState = {
|
|
525
|
-
direction,
|
|
526
|
-
startX: e.clientX,
|
|
527
|
-
startY: e.clientY,
|
|
528
|
-
startWinX: this.x,
|
|
529
|
-
startWinY: this.y,
|
|
530
|
-
startWidth: this.width,
|
|
531
|
-
startHeight: this.height
|
|
532
|
-
};
|
|
533
|
-
document.addEventListener("pointermove", this._boundOnPointerMove);
|
|
534
|
-
document.addEventListener("pointerup", this._boundOnPointerUp);
|
|
535
|
-
}
|
|
536
|
-
_handleResize(e) {
|
|
537
|
-
const state = this._resizeState;
|
|
538
|
-
if (!state) return;
|
|
539
|
-
const dx = e.clientX - state.startX;
|
|
540
|
-
const dy = e.clientY - state.startY;
|
|
541
|
-
const dir = state.direction;
|
|
542
|
-
const maxWidth = window.innerWidth;
|
|
543
|
-
const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;
|
|
544
|
-
let newX = state.startWinX;
|
|
545
|
-
let newY = state.startWinY;
|
|
546
|
-
let newW = state.startWidth;
|
|
547
|
-
let newH = state.startHeight;
|
|
548
|
-
if (dir.includes("e")) {
|
|
549
|
-
newW = Math.max(this.minWidth, Math.min(state.startWidth + dx, maxWidth - newX));
|
|
550
|
-
}
|
|
551
|
-
if (dir.includes("w")) {
|
|
552
|
-
const maxDx = state.startWidth - this.minWidth;
|
|
553
|
-
const actualDx = Math.min(dx, maxDx);
|
|
554
|
-
newX = Math.max(0, state.startWinX + actualDx);
|
|
555
|
-
newW = state.startWidth - (newX - state.startWinX);
|
|
556
|
-
}
|
|
557
|
-
if (dir.includes("s")) {
|
|
558
|
-
newH = Math.max(this.minHeight, Math.min(state.startHeight + dy, maxHeight - (newY - TOP_BAR_HEIGHT)));
|
|
559
|
-
}
|
|
560
|
-
if (dir.includes("n")) {
|
|
561
|
-
const maxDy = state.startHeight - this.minHeight;
|
|
562
|
-
const actualDy = Math.min(dy, maxDy);
|
|
563
|
-
newY = Math.max(TOP_BAR_HEIGHT, state.startWinY + actualDy);
|
|
564
|
-
newH = state.startHeight - (newY - state.startWinY);
|
|
565
|
-
}
|
|
566
|
-
this.x = newX;
|
|
567
|
-
this.y = newY;
|
|
568
|
-
this.width = newW;
|
|
569
|
-
this.height = newH;
|
|
570
|
-
this._applyPosition();
|
|
571
|
-
}
|
|
572
|
-
// ========== Keyboard Handling ==========
|
|
573
|
-
_onKeyDown(e) {
|
|
574
|
-
if (!this.isVisible || this.isMinimized) return;
|
|
575
|
-
const topWindow = Array.from(_activeWindows.values()).filter((w) => w.isVisible && !w.isMinimized).sort((a, b) => b.zIndex - a.zIndex)[0];
|
|
576
|
-
if (topWindow !== this) return;
|
|
577
|
-
if (e.key === "Escape") {
|
|
578
|
-
this.close();
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
if (e.key === "Tab" && this.modal && this.element) {
|
|
582
|
-
const focusables = _collectFocusable(this.element);
|
|
583
|
-
if (focusables.length === 0) {
|
|
584
|
-
e.preventDefault();
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
const first = focusables[0];
|
|
588
|
-
const last = focusables[focusables.length - 1];
|
|
589
|
-
const active = document.activeElement;
|
|
590
|
-
if (e.shiftKey) {
|
|
591
|
-
if (active === first || !this.element.contains(active)) {
|
|
592
|
-
e.preventDefault();
|
|
593
|
-
last.focus();
|
|
594
|
-
}
|
|
595
|
-
} else {
|
|
596
|
-
if (active === last || !this.element.contains(active)) {
|
|
597
|
-
e.preventDefault();
|
|
598
|
-
first.focus();
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
// ========== Static Methods ==========
|
|
604
|
-
/**
|
|
605
|
-
* Get a window by ID.
|
|
606
|
-
*/
|
|
607
|
-
static get(id) {
|
|
608
|
-
return _activeWindows.get(id) || null;
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Restore a minimized window by ID.
|
|
612
|
-
*/
|
|
613
|
-
static restore(id) {
|
|
614
|
-
const win = _activeWindows.get(id);
|
|
615
|
-
if (win && win.isMinimized) {
|
|
616
|
-
win._restore();
|
|
617
|
-
win.bringToFront();
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
};
|
|
621
|
-
|
|
622
|
-
export {
|
|
623
|
-
ManagedWindow
|
|
624
|
-
};
|
|
625
|
-
//# sourceMappingURL=chunk-UCJ2WD4D.js.map
|