flexdesk 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.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/THIRD_PARTY_LICENSES.md +53 -0
  4. package/css/base.css +3439 -0
  5. package/css/flexdesk.css +5797 -0
  6. package/css/overrides.css +2349 -0
  7. package/css/reset.css +46 -0
  8. package/css/tokens.css +137 -0
  9. package/dist/charts.js +780 -0
  10. package/dist/charts.js.map +7 -0
  11. package/dist/chunk-3PHCPZHT.js +222 -0
  12. package/dist/chunk-3PHCPZHT.js.map +7 -0
  13. package/dist/chunk-CT4YXXLP.js +1770 -0
  14. package/dist/chunk-CT4YXXLP.js.map +7 -0
  15. package/dist/chunk-DRYCDMEG.js +2217 -0
  16. package/dist/chunk-DRYCDMEG.js.map +7 -0
  17. package/dist/chunk-DVU44T77.js +855 -0
  18. package/dist/chunk-DVU44T77.js.map +7 -0
  19. package/dist/chunk-FL5KFNQH.js +640 -0
  20. package/dist/chunk-FL5KFNQH.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +117 -0
  22. package/dist/chunk-FOOS3T5L.js.map +7 -0
  23. package/dist/chunk-JYWURG5T.js +32 -0
  24. package/dist/chunk-JYWURG5T.js.map +7 -0
  25. package/dist/chunk-TLZUUFOE.js +276 -0
  26. package/dist/chunk-TLZUUFOE.js.map +7 -0
  27. package/dist/chunk-UCJ2WD4D.js +625 -0
  28. package/dist/chunk-UCJ2WD4D.js.map +7 -0
  29. package/dist/chunk-WVFGV5FT.js +205 -0
  30. package/dist/chunk-WVFGV5FT.js.map +7 -0
  31. package/dist/core.js +1098 -0
  32. package/dist/core.js.map +7 -0
  33. package/dist/editor.js +1661 -0
  34. package/dist/editor.js.map +7 -0
  35. package/dist/flexdesk.css +5797 -0
  36. package/dist/host.js +132 -0
  37. package/dist/host.js.map +7 -0
  38. package/dist/reset.css +46 -0
  39. package/dist/tile_registry-6WZPTUZV.js +28 -0
  40. package/dist/tile_registry-6WZPTUZV.js.map +7 -0
  41. package/dist/tiles.js +2068 -0
  42. package/dist/tiles.js.map +7 -0
  43. package/dist/tokens.css +137 -0
  44. package/dist/widgets.js +5438 -0
  45. package/dist/widgets.js.map +7 -0
  46. package/dist/wm.js +4830 -0
  47. package/dist/wm.js.map +7 -0
  48. package/package.json +80 -0
  49. package/src/charts/chart_types.js +699 -0
  50. package/src/charts/data_series_plot_window.js +761 -0
  51. package/src/charts/downsample.js +119 -0
  52. package/src/charts/plot_config.js +176 -0
  53. package/src/charts/plot_popout_window.js +1560 -0
  54. package/src/charts/plotly_wrapper.js +432 -0
  55. package/src/core/event_bus.js +230 -0
  56. package/src/core/logging.js +300 -0
  57. package/src/core/settings.js +813 -0
  58. package/src/core/state_guard.js +382 -0
  59. package/src/core/state_guard_config.js +309 -0
  60. package/src/core/state_machine.js +258 -0
  61. package/src/editor/editor_pane.js +264 -0
  62. package/src/editor/monaco_editor_factory.js +449 -0
  63. package/src/editor/monaco_loader.js +74 -0
  64. package/src/editor/notebook_search.js +460 -0
  65. package/src/editor/notebook_tab_bar.js +456 -0
  66. package/src/editor/notebook_undo_manager.js +108 -0
  67. package/src/editor/split_pane_container.js +197 -0
  68. package/src/help/help_modal.js +409 -0
  69. package/src/help/help_registry.js +65 -0
  70. package/src/host/host.js +69 -0
  71. package/src/host/pywebview_host.js +133 -0
  72. package/src/tiles/config_schema.js +256 -0
  73. package/src/tiles/layout_persistence.js +662 -0
  74. package/src/tiles/tile_base.js +785 -0
  75. package/src/tiles/tile_grid.js +905 -0
  76. package/src/tiles/tile_registry.js +136 -0
  77. package/src/tiling/command_palette.js +328 -0
  78. package/src/tiling/content_registry.js +110 -0
  79. package/src/tiling/desktops.js +133 -0
  80. package/src/tiling/entity_sources.js +179 -0
  81. package/src/tiling/keymap.js +156 -0
  82. package/src/tiling/kind_taxonomy.js +172 -0
  83. package/src/tiling/landing_table.js +717 -0
  84. package/src/tiling/loading_overlay.js +33 -0
  85. package/src/tiling/nav_panel.js +142 -0
  86. package/src/tiling/page_factory.js +215 -0
  87. package/src/tiling/panel_keys.js +145 -0
  88. package/src/tiling/shell.js +437 -0
  89. package/src/tiling/tile_breadcrumb.js +222 -0
  90. package/src/tiling/tile_renderer.js +459 -0
  91. package/src/tiling/tile_tab_menu.js +340 -0
  92. package/src/tiling/tile_tree.js +880 -0
  93. package/src/tiling/wm.js +1352 -0
  94. package/src/ui/base/component_base.js +30 -0
  95. package/src/ui/base/controller_base.js +48 -0
  96. package/src/ui/base/page_base.js +102 -0
  97. package/src/ui/components/about_dialog.js +224 -0
  98. package/src/ui/components/action_dropdown.js +389 -0
  99. package/src/ui/components/attribute_list_editor.js +652 -0
  100. package/src/ui/components/autocomplete_field.js +732 -0
  101. package/src/ui/components/computing_status_window.js +81 -0
  102. package/src/ui/components/confirm_dialog.js +307 -0
  103. package/src/ui/components/context_menu.js +87 -0
  104. package/src/ui/components/data_table.js +2228 -0
  105. package/src/ui/components/detail_header.js +177 -0
  106. package/src/ui/components/drag_reorder.js +0 -0
  107. package/src/ui/components/gallery_picker.js +147 -0
  108. package/src/ui/components/inline_renamer.js +129 -0
  109. package/src/ui/components/managed_window.js +804 -0
  110. package/src/ui/components/modal.js +512 -0
  111. package/src/ui/components/notification_history.js +264 -0
  112. package/src/ui/components/slide_out_panel.js +185 -0
  113. package/src/ui/components/slider_field.js +122 -0
  114. package/src/ui/components/sortable_list.js +309 -0
  115. package/src/ui/components/table_state_store.js +81 -0
  116. package/src/ui/components/toast.js +59 -0
  117. package/src/ui/components/tree_view.js +460 -0
  118. package/src/ui/components/window_taskbar.js +143 -0
  119. package/src/ui/controllers/panel_state_machine.js +634 -0
  120. package/src/ui/controllers/window_chrome_controller.js +659 -0
  121. package/src/ui/notification_center.js +576 -0
  122. package/src/ui/utils/overlay_scrollbar.js +492 -0
  123. package/src/ui/utils/raf_resize_observer.js +31 -0
  124. package/src/ui/utils/tooltip_service.js +406 -0
@@ -0,0 +1,625 @@
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