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,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ui/components/managed_window.js"],
4
+ "sourcesContent": ["/**\n * ManagedWindow - A reusable window component with drag, resize, minimize, and persistence.\n *\n * Usage:\n * const win = new ManagedWindow({\n * id: 'my-window',\n * title: 'My Window',\n * icon: 'folder_open', // Material Symbols icon name (optional)\n * content: myDomElement,\n * minWidth: 400,\n * minHeight: 300,\n * defaultWidth: 600,\n * defaultHeight: 400,\n * canMaximize: true,\n * onClose: () => { ... }\n * });\n * win.show();\n */\n\nimport { getSetting } from '../../core/settings.js';\n\nconst STORAGE_KEY = 'ecosim.managedWindows.v1';\nconst BASE_Z_INDEX = 6000;\nconst MAX_Z_INDEX = 6999;\n\n// Bar heights for maximize bounds. Must match the tiling shell's\n// own constants (host.css locks `.global-bottom-bar` to 22 px) so a\n// maximized window stops above the bottom bar instead of slipping\n// under it.\nconst TOP_BAR_HEIGHT = 35;\nconst BOTTOM_BAR_HEIGHT = 22;\n\n// Default icon for windows\nconst DEFAULT_ICON = 'web_asset';\n\n// Global state for z-order management\nlet _zIndexCounter = 0;\nlet _activeWindows = new Map(); // id -> ManagedWindow instance\n\n/** Collect the focusable elements inside `root`, in DOM order. Used\n * by the modal focus trap so Tab cycles within the modal frame and\n * can't escape into the underlying tiles. */\nfunction _collectFocusable(root) {\n if (!root) return [];\n const sel = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled]):not([type=\"hidden\"])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n ].join(',');\n return Array.from(root.querySelectorAll(sel)).filter((el) => {\n // Skip hidden / zero-size elements \u2014 Tab visits only what the\n // user can actually see.\n if (el.hidden) return false;\n if (el.closest('[hidden]')) return false;\n const r = el.getBoundingClientRect();\n return r.width > 0 || r.height > 0;\n });\n}\n\n// Global state cache\nlet _stateCache = null;\n\nfunction _loadState() {\n if (_stateCache) return _stateCache;\n try {\n const raw = localStorage.getItem(STORAGE_KEY);\n _stateCache = raw ? JSON.parse(raw) : {};\n } catch {\n _stateCache = {};\n }\n return _stateCache;\n}\n\nfunction _saveState(state) {\n _stateCache = state;\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(state));\n } catch (err) {\n console.warn('[ManagedWindow] Failed to save state:', err);\n }\n}\n\nfunction _getWindowState(id) {\n const state = _loadState();\n return state[id] || null;\n}\n\nfunction _setWindowState(id, windowState) {\n const state = _loadState();\n state[id] = windowState;\n _saveState(state);\n}\n\nexport class ManagedWindow {\n /**\n * @param {Object} options\n * @param {string} options.id - Unique window ID for persistence\n * @param {string} options.title - Window title\n * @param {string} [options.icon] - Material Symbols icon name (default: 'web_asset')\n * @param {HTMLElement|Function} options.content - Content element or render function\n * @param {number} [options.minWidth=400] - Minimum width\n * @param {number} [options.minHeight=300] - Minimum height\n * @param {number} [options.defaultWidth=600] - Default width\n * @param {number} [options.defaultHeight=400] - Default height\n * @param {boolean} [options.canMinimize=true] - Whether minimize button is shown\n * @param {boolean} [options.canMaximize=true] - Whether maximize button is shown\n * @param {boolean} [options.canResize=true] - Whether window can be resized\n * @param {boolean} [options.canDrag=true] - Whether window can be dragged\n * @param {boolean} [options.modal=false] - Whether to show backdrop\n * @param {Function} [options.onClose] - Callback when window is closed\n * @param {Function} [options.beforeClose] - Guard called before close. Return false (or a Promise resolving to false) to prevent closing.\n * @param {Function} [options.onMinimize] - Callback when window is minimized\n */\n constructor(options) {\n this.id = options.id;\n this.title = options.title || 'Window';\n this.icon = options.icon || DEFAULT_ICON;\n this.content = options.content;\n this.minWidth = options.minWidth || 400;\n this.minHeight = options.minHeight || 300;\n this.defaultWidth = options.defaultWidth || 600;\n this.defaultHeight = options.defaultHeight || 400;\n this.canMinimize = options.canMinimize ?? true;\n this.canMaximize = options.canMaximize ?? true;\n this.canResize = options.canResize ?? true;\n this.modal = options.modal ?? false;\n // Backdrop appearance (modal only). `undefined` \u2192 inherit the CSS\n // default (blur 2px over a 50% dim). Override per-modal when the\n // background should stay legible \u2014 e.g. a live-applied editor where\n // you want to watch the content behind update.\n // backdropBlur: blur radius in px; 0 disables the blur.\n // backdropOpacity: dim darkness, 0 (clear) \u2026 1 (opaque black).\n this.backdropBlur = options.backdropBlur;\n this.backdropOpacity = options.backdropOpacity;\n // Modals are non-draggable by default\n this.canDrag = options.canDrag ?? !this.modal;\n this.onClose = options.onClose;\n this.beforeClose = options.beforeClose || null;\n this.onMinimize = options.onMinimize;\n\n this.element = null;\n this.backdropElement = null;\n this.contentContainer = null;\n this.isVisible = false;\n this.isMinimized = false;\n this.isMaximized = false;\n this.zIndex = BASE_Z_INDEX;\n\n // Position/size state - clamp to viewport bounds\n this.x = 0;\n this.y = 0;\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;\n this.width = Math.min(this.defaultWidth, maxWidth);\n this.height = Math.min(this.defaultHeight, maxHeight);\n\n // State before maximize (for restore)\n this._preMaximizeState = null;\n\n // Drag state\n this._dragState = null;\n this._resizeState = null;\n\n // Bound handlers for cleanup\n this._boundOnPointerMove = this._onPointerMove.bind(this);\n this._boundOnPointerUp = this._onPointerUp.bind(this);\n this._boundOnKeyDown = this._onKeyDown.bind(this);\n\n // Register globally\n _activeWindows.set(this.id, this);\n }\n\n /**\n * Show the window.\n */\n show() {\n if (this.isVisible && !this.isMinimized) {\n this.bringToFront();\n return;\n }\n\n if (!this.element) {\n this._build();\n this._restoreState();\n }\n\n if (this.isMinimized) {\n this._restore();\n } else {\n this._applyPosition();\n document.body.appendChild(this.element);\n if (this.modal && this.backdropElement) {\n document.body.appendChild(this.backdropElement);\n }\n this.isVisible = true;\n }\n\n this.bringToFront();\n document.addEventListener('keydown', this._boundOnKeyDown);\n }\n\n /**\n * Hide/close the window.\n * @param {{ force?: boolean }} [options] - Pass force:true to bypass the beforeClose guard.\n */\n close({ force = false } = {}) {\n if (!this.isVisible) return;\n\n if (!force && this.beforeClose) {\n const result = this.beforeClose();\n if (result && typeof result.then === 'function') {\n result.then(allowed => { if (allowed !== false) this._doClose(); });\n return;\n }\n if (result === false) return;\n }\n\n this._doClose();\n }\n\n /** Internal close \u2014 always executes, no guard. */\n _doClose() {\n if (!this.isVisible) return;\n\n this._saveCurrentState();\n\n // Call onClose BEFORE removing elements so content can still read its state\n // (e.g., expression modal needs to read the editor value before DOM is detached)\n if (this.onClose) {\n this.onClose();\n }\n\n if (this.element && this.element.parentNode) {\n this.element.parentNode.removeChild(this.element);\n }\n if (this.backdropElement && this.backdropElement.parentNode) {\n this.backdropElement.parentNode.removeChild(this.backdropElement);\n }\n\n this.isVisible = false;\n this.isMinimized = false;\n document.removeEventListener('keydown', this._boundOnKeyDown);\n\n // Notify taskbar\n window.dispatchEvent(new CustomEvent('managed-window-closed', { detail: { id: this.id } }));\n }\n\n /**\n * Minimize to taskbar with animation.\n */\n minimize() {\n if (!this.isVisible || this.isMinimized) return;\n\n this._saveCurrentState();\n this.isMinimized = true;\n\n if (this.onMinimize) {\n this.onMinimize();\n }\n\n if (this.backdropElement) {\n this.backdropElement.style.display = 'none';\n }\n\n // Dispatch event first so the taskbar button is created synchronously\n window.dispatchEvent(new CustomEvent('managed-window-minimized', {\n detail: { id: this.id, title: this.title, icon: this.icon }\n }));\n\n if (!this.element) return;\n\n if (!getSetting('window.animateMinimize', true)) {\n this.element.style.display = 'none';\n return;\n }\n\n // Calculate animation target toward the taskbar button\n this._setMinimizeTargetProperties();\n this.element.classList.add('twm-managed-window--minimizing');\n\n setTimeout(() => {\n if (this.element) {\n this.element.style.display = 'none';\n this.element.classList.remove('twm-managed-window--minimizing');\n this._clearTargetProperties();\n }\n }, 200);\n }\n\n /**\n * Restore from minimized state with animation.\n */\n _restore() {\n if (!this.isMinimized) return;\n\n // Capture taskbar item rect BEFORE dispatching the event (which removes it)\n const taskbarRect = this._getTaskbarItemRect();\n\n if (this.element) {\n this.element.style.display = '';\n\n const animate = getSetting('window.animateMinimize', true);\n if (animate) {\n this._setRestoreTargetProperties(taskbarRect);\n this.element.classList.add('twm-managed-window--restoring');\n setTimeout(() => {\n if (this.element) {\n this.element.classList.remove('twm-managed-window--restoring');\n this._clearTargetProperties();\n }\n }, 250);\n }\n }\n if (this.modal && this.backdropElement) {\n this.backdropElement.style.display = '';\n }\n\n this.isMinimized = false;\n this.bringToFront();\n\n // Notify taskbar (removes the taskbar button)\n window.dispatchEvent(new CustomEvent('managed-window-restored', { detail: { id: this.id } }));\n }\n\n /**\n * Toggle maximize state.\n */\n toggleMaximize() {\n if (!this.canMaximize) return;\n\n if (this.isMaximized) {\n // Restore\n if (this._preMaximizeState) {\n this.x = this._preMaximizeState.x;\n this.y = this._preMaximizeState.y;\n this.width = this._preMaximizeState.width;\n this.height = this._preMaximizeState.height;\n this._preMaximizeState = null;\n }\n this.isMaximized = false;\n } else {\n // Maximize - respect top and bottom bars\n this._preMaximizeState = { x: this.x, y: this.y, width: this.width, height: this.height };\n this.x = 0;\n this.y = TOP_BAR_HEIGHT;\n this.width = window.innerWidth;\n this.height = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;\n this.isMaximized = true;\n }\n\n this._applyPosition();\n this._saveCurrentState();\n }\n\n /**\n * Find the taskbar button for this window.\n * @returns {DOMRect|null}\n */\n _getTaskbarItemRect() {\n const btn = document.querySelector(`.twm-bar-windows__item[data-window-id=\"${this.id}\"]`);\n return btn ? btn.getBoundingClientRect() : null;\n }\n\n /**\n * Set CSS custom properties to animate minimize toward the taskbar item.\n */\n _setMinimizeTargetProperties() {\n const targetRect = this._getTaskbarItemRect();\n if (!targetRect || !this.element) return;\n\n const winRect = this.element.getBoundingClientRect();\n const winCenterX = winRect.left + winRect.width / 2;\n const winCenterY = winRect.top + winRect.height / 2;\n const targetCenterX = targetRect.left + targetRect.width / 2;\n const targetCenterY = targetRect.top + targetRect.height / 2;\n\n const dx = targetCenterX - winCenterX;\n const dy = targetCenterY - winCenterY;\n const scale = Math.min(targetRect.width / winRect.width, targetRect.height / winRect.height, 0.15);\n\n this.element.style.setProperty('--mw-target-x', `${dx}px`);\n this.element.style.setProperty('--mw-target-y', `${dy}px`);\n this.element.style.setProperty('--mw-target-scale', scale);\n }\n\n /**\n * Set CSS custom properties to animate restore from the taskbar item position.\n * @param {DOMRect|null} taskbarRect\n */\n _setRestoreTargetProperties(taskbarRect) {\n if (!taskbarRect || !this.element) return;\n\n const winRect = this.element.getBoundingClientRect();\n const winCenterX = winRect.left + winRect.width / 2;\n const winCenterY = winRect.top + winRect.height / 2;\n const targetCenterX = taskbarRect.left + taskbarRect.width / 2;\n const targetCenterY = taskbarRect.top + taskbarRect.height / 2;\n\n const dx = targetCenterX - winCenterX;\n const dy = targetCenterY - winCenterY;\n const scale = Math.min(taskbarRect.width / winRect.width, taskbarRect.height / winRect.height, 0.15);\n\n this.element.style.setProperty('--mw-target-x', `${dx}px`);\n this.element.style.setProperty('--mw-target-y', `${dy}px`);\n this.element.style.setProperty('--mw-target-scale', scale);\n }\n\n /**\n * Clear animation CSS custom properties.\n */\n _clearTargetProperties() {\n if (!this.element) return;\n this.element.style.removeProperty('--mw-target-x');\n this.element.style.removeProperty('--mw-target-y');\n this.element.style.removeProperty('--mw-target-scale');\n }\n\n /**\n * Bring window to front of z-order.\n */\n bringToFront() {\n _zIndexCounter++;\n this.zIndex = Math.min(BASE_Z_INDEX + _zIndexCounter, MAX_Z_INDEX);\n if (this.element) {\n this.element.style.zIndex = this.zIndex.toString();\n }\n if (this.backdropElement) {\n this.backdropElement.style.zIndex = (this.zIndex - 1).toString();\n }\n }\n\n /**\n * Update window title.\n */\n setTitle(title) {\n this.title = title;\n if (this.element) {\n const titleEl = this.element.querySelector('.twm-managed-window__title');\n if (titleEl) titleEl.textContent = title;\n }\n }\n\n // ========== Private Methods ==========\n\n _build() {\n // Create backdrop for modal windows\n if (this.modal) {\n this.backdropElement = document.createElement('div');\n this.backdropElement.className = 'twm-managed-window__backdrop';\n // Apply backdrop overrides as inline styles only when the caller\n // set them; otherwise the CSS default governs (single source of\n // truth for the default look).\n if (this.backdropBlur !== undefined && this.backdropBlur !== null) {\n this.backdropElement.style.backdropFilter =\n this.backdropBlur > 0 ? `blur(${this.backdropBlur}px)` : 'none';\n }\n if (this.backdropOpacity !== undefined && this.backdropOpacity !== null) {\n this.backdropElement.style.background =\n `rgba(0, 0, 0, ${this.backdropOpacity})`;\n }\n }\n\n // Create window element\n this.element = document.createElement('div');\n this.element.className = 'twm-managed-window';\n if (!this.canDrag) {\n this.element.classList.add('twm-managed-window--no-drag');\n }\n if (this.modal) {\n this.element.classList.add('twm-managed-window--modal');\n }\n this.element.setAttribute('data-window-id', this.id);\n\n // Top bar\n const topbar = document.createElement('div');\n topbar.className = 'twm-managed-window__topbar';\n\n // Icon\n const icon = document.createElement('span');\n icon.className = 'twm-managed-window__icon material-symbols-outlined';\n icon.textContent = this.icon;\n\n const title = document.createElement('div');\n title.className = 'twm-managed-window__title';\n title.textContent = this.title;\n\n const buttons = document.createElement('div');\n buttons.className = 'twm-managed-window__buttons';\n\n // Minimize button (optional)\n if (this.canMinimize) {\n const minBtn = document.createElement('button');\n minBtn.className = 'twm-managed-window__btn managed-window__btn--minimize';\n minBtn.type = 'button';\n 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>';\n minBtn.title = 'Minimize';\n minBtn.addEventListener('click', (e) => { e.stopPropagation(); this.minimize(); });\n buttons.appendChild(minBtn);\n }\n\n // Maximize button (optional)\n if (this.canMaximize) {\n const maxBtn = document.createElement('button');\n maxBtn.className = 'twm-managed-window__btn managed-window__btn--maximize';\n maxBtn.type = 'button';\n 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>';\n maxBtn.title = 'Maximize';\n maxBtn.addEventListener('click', (e) => { e.stopPropagation(); this.toggleMaximize(); });\n buttons.appendChild(maxBtn);\n }\n\n // Close button\n const closeBtn = document.createElement('button');\n closeBtn.className = 'twm-managed-window__btn twm-managed-window__btn--close';\n closeBtn.type = 'button';\n 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>';\n closeBtn.title = 'Close';\n closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.close(); });\n buttons.appendChild(closeBtn);\n\n topbar.appendChild(icon);\n topbar.appendChild(title);\n topbar.appendChild(buttons);\n\n // Content container\n this.contentContainer = document.createElement('div');\n this.contentContainer.className = 'twm-managed-window__content';\n\n if (this.content instanceof HTMLElement) {\n this.contentContainer.appendChild(this.content);\n } else if (typeof this.content === 'function') {\n const rendered = this.content();\n if (rendered instanceof HTMLElement) {\n this.contentContainer.appendChild(rendered);\n }\n }\n\n this.element.appendChild(topbar);\n this.element.appendChild(this.contentContainer);\n\n // Resize handles (only if resizable)\n if (this.canResize) {\n this._addResizeHandles();\n }\n\n // Event listeners\n topbar.addEventListener('pointerdown', (e) => this._onTopbarPointerDown(e));\n if (this.canMaximize) {\n topbar.addEventListener('dblclick', () => this.toggleMaximize());\n }\n this.element.addEventListener('pointerdown', () => this.bringToFront());\n }\n\n _addResizeHandles() {\n const directions = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];\n for (const dir of directions) {\n const handle = document.createElement('div');\n handle.className = `twm-managed-window__resize managed-window__resize--${dir}`;\n handle.addEventListener('pointerdown', (e) => this._onResizePointerDown(e, dir));\n this.element.appendChild(handle);\n }\n }\n\n _applyPosition() {\n if (!this.element) return;\n\n // Calculate max available dimensions\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;\n\n // Clamp width and height to viewport (but respect minWidth/minHeight)\n this.width = Math.max(this.minWidth, Math.min(this.width, maxWidth));\n this.height = Math.max(this.minHeight, Math.min(this.height, maxHeight));\n\n // Ensure window is within viewport (respect top and bottom bars)\n const maxX = Math.max(0, maxWidth - this.width);\n const maxY = Math.max(TOP_BAR_HEIGHT, window.innerHeight - this.height - BOTTOM_BAR_HEIGHT);\n this.x = Math.max(0, Math.min(this.x, maxX));\n this.y = Math.max(TOP_BAR_HEIGHT, Math.min(this.y, maxY));\n\n this.element.style.left = `${this.x}px`;\n this.element.style.top = `${this.y}px`;\n this.element.style.width = `${this.width}px`;\n this.element.style.height = `${this.height}px`;\n }\n\n _restoreState() {\n // Calculate max available dimensions\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;\n\n // Modal windows always center on screen - never restore saved position\n const saved = this.modal ? null : _getWindowState(this.id);\n if (saved) {\n this.x = saved.x ?? this.x;\n this.y = saved.y ?? this.y;\n // Clamp restored dimensions to current viewport\n this.width = Math.min(saved.width ?? this.width, maxWidth);\n this.height = Math.min(saved.height ?? this.height, maxHeight);\n this.isMaximized = saved.maximized ?? false;\n\n if (this.isMaximized && this.canMaximize) {\n this._preMaximizeState = { x: saved.x, y: saved.y, width: saved.width, height: saved.height };\n this.x = 0;\n this.y = TOP_BAR_HEIGHT;\n this.width = maxWidth;\n this.height = maxHeight;\n }\n } else {\n // Center on screen (between top and bottom bars)\n this.x = Math.max(0, (maxWidth - this.width) / 2);\n this.y = Math.max(TOP_BAR_HEIGHT, TOP_BAR_HEIGHT + (maxHeight - this.height) / 2);\n }\n }\n\n _saveCurrentState() {\n // Modal windows always center - no need to persist position\n if (this.modal) return;\n\n _setWindowState(this.id, {\n x: this._preMaximizeState?.x ?? this.x,\n y: this._preMaximizeState?.y ?? this.y,\n width: this._preMaximizeState?.width ?? this.width,\n height: this._preMaximizeState?.height ?? this.height,\n maximized: this.isMaximized,\n });\n }\n\n // ========== Drag Handling ==========\n\n _onTopbarPointerDown(e) {\n // Don't start drag if clicking on buttons\n if (e.target.closest('.twm-managed-window__buttons')) return;\n if (this.isMaximized) return;\n if (!this.canDrag) return;\n\n e.preventDefault();\n this._dragState = {\n startX: e.clientX,\n startY: e.clientY,\n startWinX: this.x,\n startWinY: this.y,\n };\n\n document.addEventListener('pointermove', this._boundOnPointerMove);\n document.addEventListener('pointerup', this._boundOnPointerUp);\n }\n\n _onPointerMove(e) {\n if (this._dragState) {\n const dx = e.clientX - this._dragState.startX;\n const dy = e.clientY - this._dragState.startY;\n this.x = this._dragState.startWinX + dx;\n this.y = this._dragState.startWinY + dy;\n this._applyPosition();\n } else if (this._resizeState) {\n this._handleResize(e);\n }\n }\n\n _onPointerUp() {\n if (this._dragState || this._resizeState) {\n this._saveCurrentState();\n }\n this._dragState = null;\n this._resizeState = null;\n document.removeEventListener('pointermove', this._boundOnPointerMove);\n document.removeEventListener('pointerup', this._boundOnPointerUp);\n }\n\n // ========== Resize Handling ==========\n\n _onResizePointerDown(e, direction) {\n if (this.isMaximized) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n this._resizeState = {\n direction,\n startX: e.clientX,\n startY: e.clientY,\n startWinX: this.x,\n startWinY: this.y,\n startWidth: this.width,\n startHeight: this.height,\n };\n\n document.addEventListener('pointermove', this._boundOnPointerMove);\n document.addEventListener('pointerup', this._boundOnPointerUp);\n }\n\n _handleResize(e) {\n const state = this._resizeState;\n if (!state) return;\n\n const dx = e.clientX - state.startX;\n const dy = e.clientY - state.startY;\n const dir = state.direction;\n\n // Calculate max available dimensions\n const maxWidth = window.innerWidth;\n const maxHeight = window.innerHeight - TOP_BAR_HEIGHT - BOTTOM_BAR_HEIGHT;\n\n let newX = state.startWinX;\n let newY = state.startWinY;\n let newW = state.startWidth;\n let newH = state.startHeight;\n\n // Handle horizontal resize\n if (dir.includes('e')) {\n newW = Math.max(this.minWidth, Math.min(state.startWidth + dx, maxWidth - newX));\n }\n if (dir.includes('w')) {\n const maxDx = state.startWidth - this.minWidth;\n const actualDx = Math.min(dx, maxDx);\n newX = Math.max(0, state.startWinX + actualDx);\n newW = state.startWidth - (newX - state.startWinX);\n }\n\n // Handle vertical resize\n if (dir.includes('s')) {\n newH = Math.max(this.minHeight, Math.min(state.startHeight + dy, maxHeight - (newY - TOP_BAR_HEIGHT)));\n }\n if (dir.includes('n')) {\n const maxDy = state.startHeight - this.minHeight;\n const actualDy = Math.min(dy, maxDy);\n newY = Math.max(TOP_BAR_HEIGHT, state.startWinY + actualDy);\n newH = state.startHeight - (newY - state.startWinY);\n }\n\n this.x = newX;\n this.y = newY;\n this.width = newW;\n this.height = newH;\n this._applyPosition();\n }\n\n // ========== Keyboard Handling ==========\n\n _onKeyDown(e) {\n if (!this.isVisible || this.isMinimized) return;\n // Only the top-most window owns global keydowns \u2014 otherwise a\n // background window would steal focus from an open modal.\n const topWindow = Array.from(_activeWindows.values())\n .filter(w => w.isVisible && !w.isMinimized)\n .sort((a, b) => b.zIndex - a.zIndex)[0];\n if (topWindow !== this) return;\n\n if (e.key === 'Escape') {\n this.close();\n return;\n }\n // Focus trap for modals: Tab and Shift+Tab cycle through the\n // focusable elements inside the modal's frame instead of\n // escaping to underlying tiles. Without this, Tab moves the\n // active element into the background \u2014 bad UX, and a\n // genuine WCAG focus-trap violation for accessibility tools.\n if (e.key === 'Tab' && this.modal && this.element) {\n const focusables = _collectFocusable(this.element);\n if (focusables.length === 0) {\n e.preventDefault();\n return;\n }\n const first = focusables[0];\n const last = focusables[focusables.length - 1];\n const active = document.activeElement;\n if (e.shiftKey) {\n if (active === first || !this.element.contains(active)) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (active === last || !this.element.contains(active)) {\n e.preventDefault();\n first.focus();\n }\n }\n }\n }\n\n // ========== Static Methods ==========\n\n /**\n * Get a window by ID.\n */\n static get(id) {\n return _activeWindows.get(id) || null;\n }\n\n /**\n * Restore a minimized window by ID.\n */\n static restore(id) {\n const win = _activeWindows.get(id);\n if (win && win.isMinimized) {\n win._restore();\n win.bringToFront();\n }\n }\n}\n"],
5
+ "mappings": ";;;;;AAqBA,IAAM,cAAc;AACpB,IAAM,eAAe;AACrB,IAAM,cAAc;AAMpB,IAAM,iBAAiB;AACvB,IAAM,oBAAoB;AAG1B,IAAM,eAAe;AAGrB,IAAI,iBAAiB;AACrB,IAAI,iBAAiB,oBAAI,IAAI;AAK7B,SAAS,kBAAkB,MAAM;AAC7B,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,QAAM,MAAM;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ,EAAE,KAAK,GAAG;AACV,SAAO,MAAM,KAAK,KAAK,iBAAiB,GAAG,CAAC,EAAE,OAAO,CAAC,OAAO;AAGzD,QAAI,GAAG,OAAQ,QAAO;AACtB,QAAI,GAAG,QAAQ,UAAU,EAAG,QAAO;AACnC,UAAM,IAAI,GAAG,sBAAsB;AACnC,WAAO,EAAE,QAAQ,KAAK,EAAE,SAAS;AAAA,EACrC,CAAC;AACL;AAGA,IAAI,cAAc;AAElB,SAAS,aAAa;AAClB,MAAI,YAAa,QAAO;AACxB,MAAI;AACA,UAAM,MAAM,aAAa,QAAQ,WAAW;AAC5C,kBAAc,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAAA,EAC3C,QAAQ;AACJ,kBAAc,CAAC;AAAA,EACnB;AACA,SAAO;AACX;AAEA,SAAS,WAAW,OAAO;AACvB,gBAAc;AACd,MAAI;AACA,iBAAa,QAAQ,aAAa,KAAK,UAAU,KAAK,CAAC;AAAA,EAC3D,SAAS,KAAK;AACV,YAAQ,KAAK,yCAAyC,GAAG;AAAA,EAC7D;AACJ;AAEA,SAAS,gBAAgB,IAAI;AACzB,QAAM,QAAQ,WAAW;AACzB,SAAO,MAAM,EAAE,KAAK;AACxB;AAEA,SAAS,gBAAgB,IAAI,aAAa;AACtC,QAAM,QAAQ,WAAW;AACzB,QAAM,EAAE,IAAI;AACZ,aAAW,KAAK;AACpB;AAEO,IAAM,gBAAN,MAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBvB,YAAY,SAAS;AACjB,SAAK,KAAK,QAAQ;AAClB,SAAK,QAAQ,QAAQ,SAAS;AAC9B,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,UAAU,QAAQ;AACvB,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,SAAK,cAAc,QAAQ,eAAe;AAC1C,SAAK,cAAc,QAAQ,eAAe;AAC1C,SAAK,YAAY,QAAQ,aAAa;AACtC,SAAK,QAAQ,QAAQ,SAAS;AAO9B,SAAK,eAAe,QAAQ;AAC5B,SAAK,kBAAkB,QAAQ;AAE/B,SAAK,UAAU,QAAQ,WAAW,CAAC,KAAK;AACxC,SAAK,UAAU,QAAQ;AACvB,SAAK,cAAc,QAAQ,eAAe;AAC1C,SAAK,aAAa,QAAQ;AAE1B,SAAK,UAAU;AACf,SAAK,kBAAkB;AACvB,SAAK,mBAAmB;AACxB,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,SAAK,cAAc;AACnB,SAAK,SAAS;AAGd,SAAK,IAAI;AACT,SAAK,IAAI;AACT,UAAM,WAAW,OAAO;AACxB,UAAM,YAAY,OAAO,cAAc,iBAAiB;AACxD,SAAK,QAAQ,KAAK,IAAI,KAAK,cAAc,QAAQ;AACjD,SAAK,SAAS,KAAK,IAAI,KAAK,eAAe,SAAS;AAGpD,SAAK,oBAAoB;AAGzB,SAAK,aAAa;AAClB,SAAK,eAAe;AAGpB,SAAK,sBAAsB,KAAK,eAAe,KAAK,IAAI;AACxD,SAAK,oBAAoB,KAAK,aAAa,KAAK,IAAI;AACpD,SAAK,kBAAkB,KAAK,WAAW,KAAK,IAAI;AAGhD,mBAAe,IAAI,KAAK,IAAI,IAAI;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO;AACH,QAAI,KAAK,aAAa,CAAC,KAAK,aAAa;AACrC,WAAK,aAAa;AAClB;AAAA,IACJ;AAEA,QAAI,CAAC,KAAK,SAAS;AACf,WAAK,OAAO;AACZ,WAAK,cAAc;AAAA,IACvB;AAEA,QAAI,KAAK,aAAa;AAClB,WAAK,SAAS;AAAA,IAClB,OAAO;AACH,WAAK,eAAe;AACpB,eAAS,KAAK,YAAY,KAAK,OAAO;AACtC,UAAI,KAAK,SAAS,KAAK,iBAAiB;AACpC,iBAAS,KAAK,YAAY,KAAK,eAAe;AAAA,MAClD;AACA,WAAK,YAAY;AAAA,IACrB;AAEA,SAAK,aAAa;AAClB,aAAS,iBAAiB,WAAW,KAAK,eAAe;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,EAAE,QAAQ,MAAM,IAAI,CAAC,GAAG;AAC1B,QAAI,CAAC,KAAK,UAAW;AAErB,QAAI,CAAC,SAAS,KAAK,aAAa;AAC5B,YAAM,SAAS,KAAK,YAAY;AAChC,UAAI,UAAU,OAAO,OAAO,SAAS,YAAY;AAC7C,eAAO,KAAK,aAAW;AAAE,cAAI,YAAY,MAAO,MAAK,SAAS;AAAA,QAAG,CAAC;AAClE;AAAA,MACJ;AACA,UAAI,WAAW,MAAO;AAAA,IAC1B;AAEA,SAAK,SAAS;AAAA,EAClB;AAAA;AAAA,EAGA,WAAW;AACP,QAAI,CAAC,KAAK,UAAW;AAErB,SAAK,kBAAkB;AAIvB,QAAI,KAAK,SAAS;AACd,WAAK,QAAQ;AAAA,IACjB;AAEA,QAAI,KAAK,WAAW,KAAK,QAAQ,YAAY;AACzC,WAAK,QAAQ,WAAW,YAAY,KAAK,OAAO;AAAA,IACpD;AACA,QAAI,KAAK,mBAAmB,KAAK,gBAAgB,YAAY;AACzD,WAAK,gBAAgB,WAAW,YAAY,KAAK,eAAe;AAAA,IACpE;AAEA,SAAK,YAAY;AACjB,SAAK,cAAc;AACnB,aAAS,oBAAoB,WAAW,KAAK,eAAe;AAG5D,WAAO,cAAc,IAAI,YAAY,yBAAyB,EAAE,QAAQ,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;AAAA,EAC9F;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AACP,QAAI,CAAC,KAAK,aAAa,KAAK,YAAa;AAEzC,SAAK,kBAAkB;AACvB,SAAK,cAAc;AAEnB,QAAI,KAAK,YAAY;AACjB,WAAK,WAAW;AAAA,IACpB;AAEA,QAAI,KAAK,iBAAiB;AACtB,WAAK,gBAAgB,MAAM,UAAU;AAAA,IACzC;AAGA,WAAO,cAAc,IAAI,YAAY,4BAA4B;AAAA,MAC7D,QAAQ,EAAE,IAAI,KAAK,IAAI,OAAO,KAAK,OAAO,MAAM,KAAK,KAAK;AAAA,IAC9D,CAAC,CAAC;AAEF,QAAI,CAAC,KAAK,QAAS;AAEnB,QAAI,CAAC,WAAW,0BAA0B,IAAI,GAAG;AAC7C,WAAK,QAAQ,MAAM,UAAU;AAC7B;AAAA,IACJ;AAGA,SAAK,6BAA6B;AAClC,SAAK,QAAQ,UAAU,IAAI,gCAAgC;AAE3D,eAAW,MAAM;AACb,UAAI,KAAK,SAAS;AACd,aAAK,QAAQ,MAAM,UAAU;AAC7B,aAAK,QAAQ,UAAU,OAAO,gCAAgC;AAC9D,aAAK,uBAAuB;AAAA,MAChC;AAAA,IACJ,GAAG,GAAG;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AACP,QAAI,CAAC,KAAK,YAAa;AAGvB,UAAM,cAAc,KAAK,oBAAoB;AAE7C,QAAI,KAAK,SAAS;AACd,WAAK,QAAQ,MAAM,UAAU;AAE7B,YAAM,UAAU,WAAW,0BAA0B,IAAI;AACzD,UAAI,SAAS;AACT,aAAK,4BAA4B,WAAW;AAC5C,aAAK,QAAQ,UAAU,IAAI,+BAA+B;AAC1D,mBAAW,MAAM;AACb,cAAI,KAAK,SAAS;AACd,iBAAK,QAAQ,UAAU,OAAO,+BAA+B;AAC7D,iBAAK,uBAAuB;AAAA,UAChC;AAAA,QACJ,GAAG,GAAG;AAAA,MACV;AAAA,IACJ;AACA,QAAI,KAAK,SAAS,KAAK,iBAAiB;AACpC,WAAK,gBAAgB,MAAM,UAAU;AAAA,IACzC;AAEA,SAAK,cAAc;AACnB,SAAK,aAAa;AAGlB,WAAO,cAAc,IAAI,YAAY,2BAA2B,EAAE,QAAQ,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;AAAA,EAChG;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB;AACb,QAAI,CAAC,KAAK,YAAa;AAEvB,QAAI,KAAK,aAAa;AAElB,UAAI,KAAK,mBAAmB;AACxB,aAAK,IAAI,KAAK,kBAAkB;AAChC,aAAK,IAAI,KAAK,kBAAkB;AAChC,aAAK,QAAQ,KAAK,kBAAkB;AACpC,aAAK,SAAS,KAAK,kBAAkB;AACrC,aAAK,oBAAoB;AAAA,MAC7B;AACA,WAAK,cAAc;AAAA,IACvB,OAAO;AAEH,WAAK,oBAAoB,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,OAAO,KAAK,OAAO,QAAQ,KAAK,OAAO;AACxF,WAAK,IAAI;AACT,WAAK,IAAI;AACT,WAAK,QAAQ,OAAO;AACpB,WAAK,SAAS,OAAO,cAAc,iBAAiB;AACpD,WAAK,cAAc;AAAA,IACvB;AAEA,SAAK,eAAe;AACpB,SAAK,kBAAkB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB;AAClB,UAAM,MAAM,SAAS,cAAc,0CAA0C,KAAK,EAAE,IAAI;AACxF,WAAO,MAAM,IAAI,sBAAsB,IAAI;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,+BAA+B;AAC3B,UAAM,aAAa,KAAK,oBAAoB;AAC5C,QAAI,CAAC,cAAc,CAAC,KAAK,QAAS;AAElC,UAAM,UAAU,KAAK,QAAQ,sBAAsB;AACnD,UAAM,aAAa,QAAQ,OAAO,QAAQ,QAAQ;AAClD,UAAM,aAAa,QAAQ,MAAM,QAAQ,SAAS;AAClD,UAAM,gBAAgB,WAAW,OAAO,WAAW,QAAQ;AAC3D,UAAM,gBAAgB,WAAW,MAAM,WAAW,SAAS;AAE3D,UAAM,KAAK,gBAAgB;AAC3B,UAAM,KAAK,gBAAgB;AAC3B,UAAM,QAAQ,KAAK,IAAI,WAAW,QAAQ,QAAQ,OAAO,WAAW,SAAS,QAAQ,QAAQ,IAAI;AAEjG,SAAK,QAAQ,MAAM,YAAY,iBAAiB,GAAG,EAAE,IAAI;AACzD,SAAK,QAAQ,MAAM,YAAY,iBAAiB,GAAG,EAAE,IAAI;AACzD,SAAK,QAAQ,MAAM,YAAY,qBAAqB,KAAK;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,4BAA4B,aAAa;AACrC,QAAI,CAAC,eAAe,CAAC,KAAK,QAAS;AAEnC,UAAM,UAAU,KAAK,QAAQ,sBAAsB;AACnD,UAAM,aAAa,QAAQ,OAAO,QAAQ,QAAQ;AAClD,UAAM,aAAa,QAAQ,MAAM,QAAQ,SAAS;AAClD,UAAM,gBAAgB,YAAY,OAAO,YAAY,QAAQ;AAC7D,UAAM,gBAAgB,YAAY,MAAM,YAAY,SAAS;AAE7D,UAAM,KAAK,gBAAgB;AAC3B,UAAM,KAAK,gBAAgB;AAC3B,UAAM,QAAQ,KAAK,IAAI,YAAY,QAAQ,QAAQ,OAAO,YAAY,SAAS,QAAQ,QAAQ,IAAI;AAEnG,SAAK,QAAQ,MAAM,YAAY,iBAAiB,GAAG,EAAE,IAAI;AACzD,SAAK,QAAQ,MAAM,YAAY,iBAAiB,GAAG,EAAE,IAAI;AACzD,SAAK,QAAQ,MAAM,YAAY,qBAAqB,KAAK;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA,EAKA,yBAAyB;AACrB,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,QAAQ,MAAM,eAAe,eAAe;AACjD,SAAK,QAAQ,MAAM,eAAe,eAAe;AACjD,SAAK,QAAQ,MAAM,eAAe,mBAAmB;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe;AACX;AACA,SAAK,SAAS,KAAK,IAAI,eAAe,gBAAgB,WAAW;AACjE,QAAI,KAAK,SAAS;AACd,WAAK,QAAQ,MAAM,SAAS,KAAK,OAAO,SAAS;AAAA,IACrD;AACA,QAAI,KAAK,iBAAiB;AACtB,WAAK,gBAAgB,MAAM,UAAU,KAAK,SAAS,GAAG,SAAS;AAAA,IACnE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAO;AACZ,SAAK,QAAQ;AACb,QAAI,KAAK,SAAS;AACd,YAAM,UAAU,KAAK,QAAQ,cAAc,4BAA4B;AACvE,UAAI,QAAS,SAAQ,cAAc;AAAA,IACvC;AAAA,EACJ;AAAA;AAAA,EAIA,SAAS;AAEL,QAAI,KAAK,OAAO;AACZ,WAAK,kBAAkB,SAAS,cAAc,KAAK;AACnD,WAAK,gBAAgB,YAAY;AAIjC,UAAI,KAAK,iBAAiB,UAAa,KAAK,iBAAiB,MAAM;AAC/D,aAAK,gBAAgB,MAAM,iBACvB,KAAK,eAAe,IAAI,QAAQ,KAAK,YAAY,QAAQ;AAAA,MACjE;AACA,UAAI,KAAK,oBAAoB,UAAa,KAAK,oBAAoB,MAAM;AACrE,aAAK,gBAAgB,MAAM,aACvB,iBAAiB,KAAK,eAAe;AAAA,MAC7C;AAAA,IACJ;AAGA,SAAK,UAAU,SAAS,cAAc,KAAK;AAC3C,SAAK,QAAQ,YAAY;AACzB,QAAI,CAAC,KAAK,SAAS;AACf,WAAK,QAAQ,UAAU,IAAI,6BAA6B;AAAA,IAC5D;AACA,QAAI,KAAK,OAAO;AACZ,WAAK,QAAQ,UAAU,IAAI,2BAA2B;AAAA,IAC1D;AACA,SAAK,QAAQ,aAAa,kBAAkB,KAAK,EAAE;AAGnD,UAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,WAAO,YAAY;AAGnB,UAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,SAAK,YAAY;AACjB,SAAK,cAAc,KAAK;AAExB,UAAM,QAAQ,SAAS,cAAc,KAAK;AAC1C,UAAM,YAAY;AAClB,UAAM,cAAc,KAAK;AAEzB,UAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,YAAQ,YAAY;AAGpB,QAAI,KAAK,aAAa;AAClB,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,aAAO,YAAY;AACnB,aAAO,OAAO;AACd,aAAO,YAAY;AACnB,aAAO,QAAQ;AACf,aAAO,iBAAiB,SAAS,CAAC,MAAM;AAAE,UAAE,gBAAgB;AAAG,aAAK,SAAS;AAAA,MAAG,CAAC;AACjF,cAAQ,YAAY,MAAM;AAAA,IAC9B;AAGA,QAAI,KAAK,aAAa;AAClB,YAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,aAAO,YAAY;AACnB,aAAO,OAAO;AACd,aAAO,YAAY;AACnB,aAAO,QAAQ;AACf,aAAO,iBAAiB,SAAS,CAAC,MAAM;AAAE,UAAE,gBAAgB;AAAG,aAAK,eAAe;AAAA,MAAG,CAAC;AACvF,cAAQ,YAAY,MAAM;AAAA,IAC9B;AAGA,UAAM,WAAW,SAAS,cAAc,QAAQ;AAChD,aAAS,YAAY;AACrB,aAAS,OAAO;AAChB,aAAS,YAAY;AACrB,aAAS,QAAQ;AACjB,aAAS,iBAAiB,SAAS,CAAC,MAAM;AAAE,QAAE,gBAAgB;AAAG,WAAK,MAAM;AAAA,IAAG,CAAC;AAChF,YAAQ,YAAY,QAAQ;AAE5B,WAAO,YAAY,IAAI;AACvB,WAAO,YAAY,KAAK;AACxB,WAAO,YAAY,OAAO;AAG1B,SAAK,mBAAmB,SAAS,cAAc,KAAK;AACpD,SAAK,iBAAiB,YAAY;AAElC,QAAI,KAAK,mBAAmB,aAAa;AACrC,WAAK,iBAAiB,YAAY,KAAK,OAAO;AAAA,IAClD,WAAW,OAAO,KAAK,YAAY,YAAY;AAC3C,YAAM,WAAW,KAAK,QAAQ;AAC9B,UAAI,oBAAoB,aAAa;AACjC,aAAK,iBAAiB,YAAY,QAAQ;AAAA,MAC9C;AAAA,IACJ;AAEA,SAAK,QAAQ,YAAY,MAAM;AAC/B,SAAK,QAAQ,YAAY,KAAK,gBAAgB;AAG9C,QAAI,KAAK,WAAW;AAChB,WAAK,kBAAkB;AAAA,IAC3B;AAGA,WAAO,iBAAiB,eAAe,CAAC,MAAM,KAAK,qBAAqB,CAAC,CAAC;AAC1E,QAAI,KAAK,aAAa;AAClB,aAAO,iBAAiB,YAAY,MAAM,KAAK,eAAe,CAAC;AAAA,IACnE;AACA,SAAK,QAAQ,iBAAiB,eAAe,MAAM,KAAK,aAAa,CAAC;AAAA,EAC1E;AAAA,EAEA,oBAAoB;AAChB,UAAM,aAAa,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,MAAM,MAAM,IAAI;AAC9D,eAAW,OAAO,YAAY;AAC1B,YAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,aAAO,YAAY,sDAAsD,GAAG;AAC5E,aAAO,iBAAiB,eAAe,CAAC,MAAM,KAAK,qBAAqB,GAAG,GAAG,CAAC;AAC/E,WAAK,QAAQ,YAAY,MAAM;AAAA,IACnC;AAAA,EACJ;AAAA,EAEA,iBAAiB;AACb,QAAI,CAAC,KAAK,QAAS;AAGnB,UAAM,WAAW,OAAO;AACxB,UAAM,YAAY,OAAO,cAAc,iBAAiB;AAGxD,SAAK,QAAQ,KAAK,IAAI,KAAK,UAAU,KAAK,IAAI,KAAK,OAAO,QAAQ,CAAC;AACnE,SAAK,SAAS,KAAK,IAAI,KAAK,WAAW,KAAK,IAAI,KAAK,QAAQ,SAAS,CAAC;AAGvE,UAAM,OAAO,KAAK,IAAI,GAAG,WAAW,KAAK,KAAK;AAC9C,UAAM,OAAO,KAAK,IAAI,gBAAgB,OAAO,cAAc,KAAK,SAAS,iBAAiB;AAC1F,SAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,GAAG,IAAI,CAAC;AAC3C,SAAK,IAAI,KAAK,IAAI,gBAAgB,KAAK,IAAI,KAAK,GAAG,IAAI,CAAC;AAExD,SAAK,QAAQ,MAAM,OAAO,GAAG,KAAK,CAAC;AACnC,SAAK,QAAQ,MAAM,MAAM,GAAG,KAAK,CAAC;AAClC,SAAK,QAAQ,MAAM,QAAQ,GAAG,KAAK,KAAK;AACxC,SAAK,QAAQ,MAAM,SAAS,GAAG,KAAK,MAAM;AAAA,EAC9C;AAAA,EAEA,gBAAgB;AAEZ,UAAM,WAAW,OAAO;AACxB,UAAM,YAAY,OAAO,cAAc,iBAAiB;AAGxD,UAAM,QAAQ,KAAK,QAAQ,OAAO,gBAAgB,KAAK,EAAE;AACzD,QAAI,OAAO;AACP,WAAK,IAAI,MAAM,KAAK,KAAK;AACzB,WAAK,IAAI,MAAM,KAAK,KAAK;AAEzB,WAAK,QAAQ,KAAK,IAAI,MAAM,SAAS,KAAK,OAAO,QAAQ;AACzD,WAAK,SAAS,KAAK,IAAI,MAAM,UAAU,KAAK,QAAQ,SAAS;AAC7D,WAAK,cAAc,MAAM,aAAa;AAEtC,UAAI,KAAK,eAAe,KAAK,aAAa;AACtC,aAAK,oBAAoB,EAAE,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO;AAC5F,aAAK,IAAI;AACT,aAAK,IAAI;AACT,aAAK,QAAQ;AACb,aAAK,SAAS;AAAA,MAClB;AAAA,IACJ,OAAO;AAEH,WAAK,IAAI,KAAK,IAAI,IAAI,WAAW,KAAK,SAAS,CAAC;AAChD,WAAK,IAAI,KAAK,IAAI,gBAAgB,kBAAkB,YAAY,KAAK,UAAU,CAAC;AAAA,IACpF;AAAA,EACJ;AAAA,EAEA,oBAAoB;AAEhB,QAAI,KAAK,MAAO;AAEhB,oBAAgB,KAAK,IAAI;AAAA,MACrB,GAAG,KAAK,mBAAmB,KAAK,KAAK;AAAA,MACrC,GAAG,KAAK,mBAAmB,KAAK,KAAK;AAAA,MACrC,OAAO,KAAK,mBAAmB,SAAS,KAAK;AAAA,MAC7C,QAAQ,KAAK,mBAAmB,UAAU,KAAK;AAAA,MAC/C,WAAW,KAAK;AAAA,IACpB,CAAC;AAAA,EACL;AAAA;AAAA,EAIA,qBAAqB,GAAG;AAEpB,QAAI,EAAE,OAAO,QAAQ,8BAA8B,EAAG;AACtD,QAAI,KAAK,YAAa;AACtB,QAAI,CAAC,KAAK,QAAS;AAEnB,MAAE,eAAe;AACjB,SAAK,aAAa;AAAA,MACd,QAAQ,EAAE;AAAA,MACV,QAAQ,EAAE;AAAA,MACV,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,IACpB;AAEA,aAAS,iBAAiB,eAAe,KAAK,mBAAmB;AACjE,aAAS,iBAAiB,aAAa,KAAK,iBAAiB;AAAA,EACjE;AAAA,EAEA,eAAe,GAAG;AACd,QAAI,KAAK,YAAY;AACjB,YAAM,KAAK,EAAE,UAAU,KAAK,WAAW;AACvC,YAAM,KAAK,EAAE,UAAU,KAAK,WAAW;AACvC,WAAK,IAAI,KAAK,WAAW,YAAY;AACrC,WAAK,IAAI,KAAK,WAAW,YAAY;AACrC,WAAK,eAAe;AAAA,IACxB,WAAW,KAAK,cAAc;AAC1B,WAAK,cAAc,CAAC;AAAA,IACxB;AAAA,EACJ;AAAA,EAEA,eAAe;AACX,QAAI,KAAK,cAAc,KAAK,cAAc;AACtC,WAAK,kBAAkB;AAAA,IAC3B;AACA,SAAK,aAAa;AAClB,SAAK,eAAe;AACpB,aAAS,oBAAoB,eAAe,KAAK,mBAAmB;AACpE,aAAS,oBAAoB,aAAa,KAAK,iBAAiB;AAAA,EACpE;AAAA;AAAA,EAIA,qBAAqB,GAAG,WAAW;AAC/B,QAAI,KAAK,YAAa;AAEtB,MAAE,eAAe;AACjB,MAAE,gBAAgB;AAElB,SAAK,eAAe;AAAA,MAChB;AAAA,MACA,QAAQ,EAAE;AAAA,MACV,QAAQ,EAAE;AAAA,MACV,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,MACjB,aAAa,KAAK;AAAA,IACtB;AAEA,aAAS,iBAAiB,eAAe,KAAK,mBAAmB;AACjE,aAAS,iBAAiB,aAAa,KAAK,iBAAiB;AAAA,EACjE;AAAA,EAEA,cAAc,GAAG;AACb,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,MAAO;AAEZ,UAAM,KAAK,EAAE,UAAU,MAAM;AAC7B,UAAM,KAAK,EAAE,UAAU,MAAM;AAC7B,UAAM,MAAM,MAAM;AAGlB,UAAM,WAAW,OAAO;AACxB,UAAM,YAAY,OAAO,cAAc,iBAAiB;AAExD,QAAI,OAAO,MAAM;AACjB,QAAI,OAAO,MAAM;AACjB,QAAI,OAAO,MAAM;AACjB,QAAI,OAAO,MAAM;AAGjB,QAAI,IAAI,SAAS,GAAG,GAAG;AACnB,aAAO,KAAK,IAAI,KAAK,UAAU,KAAK,IAAI,MAAM,aAAa,IAAI,WAAW,IAAI,CAAC;AAAA,IACnF;AACA,QAAI,IAAI,SAAS,GAAG,GAAG;AACnB,YAAM,QAAQ,MAAM,aAAa,KAAK;AACtC,YAAM,WAAW,KAAK,IAAI,IAAI,KAAK;AACnC,aAAO,KAAK,IAAI,GAAG,MAAM,YAAY,QAAQ;AAC7C,aAAO,MAAM,cAAc,OAAO,MAAM;AAAA,IAC5C;AAGA,QAAI,IAAI,SAAS,GAAG,GAAG;AACnB,aAAO,KAAK,IAAI,KAAK,WAAW,KAAK,IAAI,MAAM,cAAc,IAAI,aAAa,OAAO,eAAe,CAAC;AAAA,IACzG;AACA,QAAI,IAAI,SAAS,GAAG,GAAG;AACnB,YAAM,QAAQ,MAAM,cAAc,KAAK;AACvC,YAAM,WAAW,KAAK,IAAI,IAAI,KAAK;AACnC,aAAO,KAAK,IAAI,gBAAgB,MAAM,YAAY,QAAQ;AAC1D,aAAO,MAAM,eAAe,OAAO,MAAM;AAAA,IAC7C;AAEA,SAAK,IAAI;AACT,SAAK,IAAI;AACT,SAAK,QAAQ;AACb,SAAK,SAAS;AACd,SAAK,eAAe;AAAA,EACxB;AAAA;AAAA,EAIA,WAAW,GAAG;AACV,QAAI,CAAC,KAAK,aAAa,KAAK,YAAa;AAGzC,UAAM,YAAY,MAAM,KAAK,eAAe,OAAO,CAAC,EAC/C,OAAO,OAAK,EAAE,aAAa,CAAC,EAAE,WAAW,EACzC,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAC1C,QAAI,cAAc,KAAM;AAExB,QAAI,EAAE,QAAQ,UAAU;AACpB,WAAK,MAAM;AACX;AAAA,IACJ;AAMA,QAAI,EAAE,QAAQ,SAAS,KAAK,SAAS,KAAK,SAAS;AAC/C,YAAM,aAAa,kBAAkB,KAAK,OAAO;AACjD,UAAI,WAAW,WAAW,GAAG;AACzB,UAAE,eAAe;AACjB;AAAA,MACJ;AACA,YAAM,QAAQ,WAAW,CAAC;AAC1B,YAAM,OAAQ,WAAW,WAAW,SAAS,CAAC;AAC9C,YAAM,SAAS,SAAS;AACxB,UAAI,EAAE,UAAU;AACZ,YAAI,WAAW,SAAS,CAAC,KAAK,QAAQ,SAAS,MAAM,GAAG;AACpD,YAAE,eAAe;AACjB,eAAK,MAAM;AAAA,QACf;AAAA,MACJ,OAAO;AACH,YAAI,WAAW,QAAQ,CAAC,KAAK,QAAQ,SAAS,MAAM,GAAG;AACnD,YAAE,eAAe;AACjB,gBAAM,MAAM;AAAA,QAChB;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,IAAI,IAAI;AACX,WAAO,eAAe,IAAI,EAAE,KAAK;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,QAAQ,IAAI;AACf,UAAM,MAAM,eAAe,IAAI,EAAE;AACjC,QAAI,OAAO,IAAI,aAAa;AACxB,UAAI,SAAS;AACb,UAAI,aAAa;AAAA,IACrB;AAAA,EACJ;AACJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,205 @@
1
+ // src/ui/components/drag_reorder.js
2
+ var DragReorder = class {
3
+ /**
4
+ * @param {Object} opts
5
+ * @param {HTMLElement} opts.container
6
+ * @param {string} [opts.itemSelector='.tab'] selector for reorderable items
7
+ * @param {string} [opts.keyAttr='path'] dataset key / data-attr identifying an item
8
+ * @param {'x'|'y'} [opts.axis='x']
9
+ * @param {string} [opts.indicatorClass='tab-drop-indicator']
10
+ * @param {boolean} [opts.autoScroll=true]
11
+ * @param {(e: DragEvent, item: HTMLElement) => boolean} [opts.draggableGuard]
12
+ * return false to cancel a drag start (e.g. from a close button)
13
+ * @param {(e: DragEvent, key: string, item: HTMLElement) => void} [opts.onDragStart]
14
+ * host hook to augment dataTransfer (e.g. add a cross-pane MIME)
15
+ * @param {(order: string[]) => void} [opts.onReorder]
16
+ */
17
+ constructor(opts = {}) {
18
+ this.container = opts.container || null;
19
+ this.itemSelector = opts.itemSelector || ".tab";
20
+ this.keyAttr = opts.keyAttr || "path";
21
+ this.axis = opts.axis === "y" ? "y" : "x";
22
+ this.indicatorClass = opts.indicatorClass || "twm-tab-drop-indicator";
23
+ this.autoScroll = opts.autoScroll !== false;
24
+ this.draggableGuard = opts.draggableGuard || null;
25
+ this.onDragStart = opts.onDragStart || null;
26
+ this.onReorder = opts.onReorder || null;
27
+ this._draggingEl = null;
28
+ this._indicatorEl = null;
29
+ this._lastSnapshot = [];
30
+ this._onOver = this._onOver.bind(this);
31
+ this._onDrop = this._onDrop.bind(this);
32
+ this._onLeave = this._onLeave.bind(this);
33
+ if (this.container) {
34
+ this.container.addEventListener("dragover", this._onOver);
35
+ this.container.addEventListener("drop", this._onDrop);
36
+ this.container.addEventListener("dragleave", this._onLeave);
37
+ }
38
+ }
39
+ /** Mark current items draggable + wire dragstart/dragend. Idempotent —
40
+ * call after every (re)render; already-wired items are skipped. */
41
+ attach() {
42
+ if (!this.container) return;
43
+ for (const item of this.container.querySelectorAll(this.itemSelector)) {
44
+ if (item.__dragReorder) continue;
45
+ item.__dragReorder = true;
46
+ item.setAttribute("draggable", "true");
47
+ item.addEventListener("dragstart", (e) => this._start(e, item));
48
+ item.addEventListener("dragend", () => this._end());
49
+ }
50
+ }
51
+ /** True while this instance is mid-drag. */
52
+ isActive() {
53
+ return !!this._draggingEl;
54
+ }
55
+ destroy() {
56
+ if (this.container) {
57
+ this.container.removeEventListener("dragover", this._onOver);
58
+ this.container.removeEventListener("drop", this._onDrop);
59
+ this.container.removeEventListener("dragleave", this._onLeave);
60
+ }
61
+ this._removeIndicator();
62
+ this._draggingEl = null;
63
+ }
64
+ // ── internals ───────────────────────────────────────────────────
65
+ _key(el) {
66
+ if (!el) return null;
67
+ return el.dataset?.[this.keyAttr] ?? el.getAttribute?.(`data-${this.keyAttr}`) ?? null;
68
+ }
69
+ _items() {
70
+ return Array.from(this.container.querySelectorAll(this.itemSelector));
71
+ }
72
+ _snapshot() {
73
+ return this._items().map((el) => this._key(el)).filter((k) => k != null);
74
+ }
75
+ _centerOf(rect) {
76
+ return this.axis === "y" ? rect.top + rect.height / 2 : rect.left + rect.width / 2;
77
+ }
78
+ _start(e, item) {
79
+ if (this.draggableGuard && !this.draggableGuard(e, item)) {
80
+ e.preventDefault();
81
+ return;
82
+ }
83
+ const key = this._key(item);
84
+ this._draggingEl = item;
85
+ this._lastSnapshot = this._snapshot();
86
+ item.classList.add("dragging");
87
+ try {
88
+ e.dataTransfer.effectAllowed = "move";
89
+ if (key != null) e.dataTransfer.setData("text/plain", String(key));
90
+ } catch {
91
+ }
92
+ this.onDragStart?.(e, key, item);
93
+ this._ensureIndicator();
94
+ this._positionByNearest(this._centerOf(item.getBoundingClientRect()));
95
+ }
96
+ _end() {
97
+ if (!this._draggingEl) return;
98
+ this._draggingEl.classList.remove("dragging");
99
+ this._draggingEl = null;
100
+ this._removeIndicator();
101
+ this._commitIfChanged();
102
+ }
103
+ _onOver(e) {
104
+ if (!this._draggingEl) return;
105
+ e.preventDefault();
106
+ try {
107
+ e.dataTransfer.dropEffect = "move";
108
+ } catch {
109
+ }
110
+ const coord = this.axis === "y" ? e.clientY : e.clientX;
111
+ this._positionByNearest(coord);
112
+ if (this.autoScroll) this._autoScrollEdge(coord);
113
+ }
114
+ _onDrop(e) {
115
+ if (!this._draggingEl) return;
116
+ e.preventDefault();
117
+ const indicator = this._ensureIndicator();
118
+ if (indicator?.parentElement) {
119
+ this.container.insertBefore(this._draggingEl, indicator);
120
+ }
121
+ this._finalize();
122
+ }
123
+ _onLeave(e) {
124
+ if (!this._draggingEl) return;
125
+ if (e.currentTarget !== this.container) return;
126
+ if (this.container.contains(e.relatedTarget)) return;
127
+ this._finalize();
128
+ }
129
+ _finalize() {
130
+ if (this._draggingEl) this._draggingEl.classList.remove("dragging");
131
+ this._removeIndicator();
132
+ this._draggingEl = null;
133
+ this._commitIfChanged();
134
+ }
135
+ _commitIfChanged() {
136
+ const order = this._snapshot();
137
+ if (!order.length) return;
138
+ if (this._lastSnapshot.join("\0") === order.join("\0")) return;
139
+ this._lastSnapshot = order;
140
+ this.onReorder?.(order);
141
+ }
142
+ _ensureIndicator() {
143
+ if (!this._indicatorEl) {
144
+ this._indicatorEl = document.createElement("div");
145
+ this._indicatorEl.className = this.indicatorClass;
146
+ this._indicatorEl.setAttribute("aria-hidden", "true");
147
+ }
148
+ if (!this._indicatorEl.parentElement) {
149
+ this.container.appendChild(this._indicatorEl);
150
+ }
151
+ return this._indicatorEl;
152
+ }
153
+ _removeIndicator() {
154
+ this._indicatorEl?.remove();
155
+ }
156
+ _positionByNearest(coord) {
157
+ if (!this.container || !this._indicatorEl || !this._draggingEl) return;
158
+ const items = this._items().filter((t) => t !== this._draggingEl);
159
+ if (!items.length) return;
160
+ let best = null;
161
+ let bestDist = Infinity;
162
+ for (const it of items) {
163
+ const center = this._centerOf(it.getBoundingClientRect());
164
+ const dist = Math.abs(coord - center);
165
+ if (dist < bestDist) {
166
+ bestDist = dist;
167
+ best = { it, center };
168
+ }
169
+ }
170
+ if (!best) return;
171
+ const after = coord >= best.center;
172
+ const ref = after ? best.it.nextElementSibling : best.it;
173
+ this.container.insertBefore(this._indicatorEl, ref || null);
174
+ }
175
+ _autoScrollEdge(coord) {
176
+ if (!this.container) return;
177
+ const rect = this.container.getBoundingClientRect();
178
+ const edge = 24;
179
+ const step = 16;
180
+ if (this.axis === "y") {
181
+ if (coord - rect.top < edge) {
182
+ this.container.scrollTop = Math.max(0, this.container.scrollTop - step);
183
+ } else if (rect.bottom - coord < edge) {
184
+ this.container.scrollTop = Math.min(
185
+ this.container.scrollHeight,
186
+ this.container.scrollTop + step
187
+ );
188
+ }
189
+ } else {
190
+ if (coord - rect.left < edge) {
191
+ this.container.scrollLeft = Math.max(0, this.container.scrollLeft - step);
192
+ } else if (rect.right - coord < edge) {
193
+ this.container.scrollLeft = Math.min(
194
+ this.container.scrollWidth,
195
+ this.container.scrollLeft + step
196
+ );
197
+ }
198
+ }
199
+ }
200
+ };
201
+
202
+ export {
203
+ DragReorder
204
+ };
205
+ //# sourceMappingURL=chunk-WVFGV5FT.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ui/components/drag_reorder.js"],
4
+ "sourcesContent": ["/**\n * DragReorder \u2014 the one same-list drag-to-reorder implementation, with\n * the \"classic\" ghost (`.dragging` on the dragged element) + drop\n * placeholder (an indicator div inserted at the drop position) and\n * edge auto-scroll. Native HTML5 drag-and-drop.\n *\n * Extracted from the duplicated tab-bar reorder code so the notebook tab\n * bar, the workspace tab bar, and the schema column strip all share one\n * implementation. Axis-aware (`x` for horizontal strips, `y` for\n * vertical lists) and parameterised so it makes no assumptions about\n * tabs specifically.\n *\n * Usage:\n * const dr = new DragReorder({\n * container, itemSelector: '.tab', keyAttr: 'tabId', axis: 'x',\n * onReorder: (order) => { ... }, // order = array of keys\n * });\n * dr.attach(); // after each (re)render of the items\n * dr.destroy(); // on teardown\n *\n * Coexistence: the container-level handlers no-op unless THIS instance's\n * drag is active, so a host can keep its own dragover/drop handlers for\n * other concerns (e.g. cross-pane tab drops) alongside it \u2014 gate those\n * on `dr.isActive() === false`.\n */\nexport class DragReorder {\n /**\n * @param {Object} opts\n * @param {HTMLElement} opts.container\n * @param {string} [opts.itemSelector='.tab'] selector for reorderable items\n * @param {string} [opts.keyAttr='path'] dataset key / data-attr identifying an item\n * @param {'x'|'y'} [opts.axis='x']\n * @param {string} [opts.indicatorClass='tab-drop-indicator']\n * @param {boolean} [opts.autoScroll=true]\n * @param {(e: DragEvent, item: HTMLElement) => boolean} [opts.draggableGuard]\n * return false to cancel a drag start (e.g. from a close button)\n * @param {(e: DragEvent, key: string, item: HTMLElement) => void} [opts.onDragStart]\n * host hook to augment dataTransfer (e.g. add a cross-pane MIME)\n * @param {(order: string[]) => void} [opts.onReorder]\n */\n constructor(opts = {}) {\n this.container = opts.container || null;\n this.itemSelector = opts.itemSelector || '.tab';\n this.keyAttr = opts.keyAttr || 'path';\n this.axis = opts.axis === 'y' ? 'y' : 'x';\n this.indicatorClass = opts.indicatorClass || 'twm-tab-drop-indicator';\n this.autoScroll = opts.autoScroll !== false;\n this.draggableGuard = opts.draggableGuard || null;\n this.onDragStart = opts.onDragStart || null;\n this.onReorder = opts.onReorder || null;\n\n this._draggingEl = null;\n this._indicatorEl = null;\n this._lastSnapshot = [];\n\n this._onOver = this._onOver.bind(this);\n this._onDrop = this._onDrop.bind(this);\n this._onLeave = this._onLeave.bind(this);\n if (this.container) {\n this.container.addEventListener('dragover', this._onOver);\n this.container.addEventListener('drop', this._onDrop);\n this.container.addEventListener('dragleave', this._onLeave);\n }\n }\n\n /** Mark current items draggable + wire dragstart/dragend. Idempotent \u2014\n * call after every (re)render; already-wired items are skipped. */\n attach() {\n if (!this.container) return;\n for (const item of this.container.querySelectorAll(this.itemSelector)) {\n if (item.__dragReorder) continue;\n item.__dragReorder = true;\n item.setAttribute('draggable', 'true');\n item.addEventListener('dragstart', (e) => this._start(e, item));\n item.addEventListener('dragend', () => this._end());\n }\n }\n\n /** True while this instance is mid-drag. */\n isActive() { return !!this._draggingEl; }\n\n destroy() {\n if (this.container) {\n this.container.removeEventListener('dragover', this._onOver);\n this.container.removeEventListener('drop', this._onDrop);\n this.container.removeEventListener('dragleave', this._onLeave);\n }\n this._removeIndicator();\n this._draggingEl = null;\n }\n\n // \u2500\u2500 internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _key(el) {\n if (!el) return null;\n return el.dataset?.[this.keyAttr]\n ?? el.getAttribute?.(`data-${this.keyAttr}`)\n ?? null;\n }\n\n _items() {\n return Array.from(this.container.querySelectorAll(this.itemSelector));\n }\n\n _snapshot() {\n return this._items().map((el) => this._key(el)).filter((k) => k != null);\n }\n\n _centerOf(rect) {\n return this.axis === 'y'\n ? rect.top + rect.height / 2\n : rect.left + rect.width / 2;\n }\n\n _start(e, item) {\n if (this.draggableGuard && !this.draggableGuard(e, item)) {\n e.preventDefault();\n return;\n }\n const key = this._key(item);\n this._draggingEl = item;\n this._lastSnapshot = this._snapshot();\n item.classList.add('dragging');\n try {\n e.dataTransfer.effectAllowed = 'move';\n if (key != null) e.dataTransfer.setData('text/plain', String(key));\n } catch { /* ignore */ }\n this.onDragStart?.(e, key, item);\n this._ensureIndicator();\n this._positionByNearest(this._centerOf(item.getBoundingClientRect()));\n }\n\n _end() {\n if (!this._draggingEl) return;\n this._draggingEl.classList.remove('dragging');\n this._draggingEl = null;\n this._removeIndicator();\n this._commitIfChanged();\n }\n\n _onOver(e) {\n if (!this._draggingEl) return; // not our drag \u2014 let the host handle it\n e.preventDefault();\n try { e.dataTransfer.dropEffect = 'move'; } catch { /* ignore */ }\n const coord = this.axis === 'y' ? e.clientY : e.clientX;\n this._positionByNearest(coord);\n if (this.autoScroll) this._autoScrollEdge(coord);\n }\n\n _onDrop(e) {\n if (!this._draggingEl) return;\n e.preventDefault();\n const indicator = this._ensureIndicator();\n if (indicator?.parentElement) {\n this.container.insertBefore(this._draggingEl, indicator);\n }\n this._finalize();\n }\n\n _onLeave(e) {\n if (!this._draggingEl) return;\n if (e.currentTarget !== this.container) return;\n if (this.container.contains(e.relatedTarget)) return;\n this._finalize();\n }\n\n _finalize() {\n if (this._draggingEl) this._draggingEl.classList.remove('dragging');\n this._removeIndicator();\n this._draggingEl = null;\n this._commitIfChanged();\n }\n\n _commitIfChanged() {\n const order = this._snapshot();\n if (!order.length) return;\n if (this._lastSnapshot.join('\u0000') === order.join('\u0000')) return;\n this._lastSnapshot = order;\n this.onReorder?.(order);\n }\n\n _ensureIndicator() {\n if (!this._indicatorEl) {\n this._indicatorEl = document.createElement('div');\n this._indicatorEl.className = this.indicatorClass;\n this._indicatorEl.setAttribute('aria-hidden', 'true');\n }\n if (!this._indicatorEl.parentElement) {\n this.container.appendChild(this._indicatorEl);\n }\n return this._indicatorEl;\n }\n\n _removeIndicator() { this._indicatorEl?.remove(); }\n\n _positionByNearest(coord) {\n if (!this.container || !this._indicatorEl || !this._draggingEl) return;\n const items = this._items().filter((t) => t !== this._draggingEl);\n if (!items.length) return;\n let best = null;\n let bestDist = Infinity;\n for (const it of items) {\n const center = this._centerOf(it.getBoundingClientRect());\n const dist = Math.abs(coord - center);\n if (dist < bestDist) { bestDist = dist; best = { it, center }; }\n }\n if (!best) return;\n const after = coord >= best.center;\n const ref = after ? best.it.nextElementSibling : best.it;\n this.container.insertBefore(this._indicatorEl, ref || null);\n }\n\n _autoScrollEdge(coord) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n const edge = 24;\n const step = 16;\n if (this.axis === 'y') {\n if (coord - rect.top < edge) {\n this.container.scrollTop = Math.max(0, this.container.scrollTop - step);\n } else if (rect.bottom - coord < edge) {\n this.container.scrollTop = Math.min(\n this.container.scrollHeight, this.container.scrollTop + step);\n }\n } else {\n if (coord - rect.left < edge) {\n this.container.scrollLeft = Math.max(0, this.container.scrollLeft - step);\n } else if (rect.right - coord < edge) {\n this.container.scrollLeft = Math.min(\n this.container.scrollWidth, this.container.scrollLeft + step);\n }\n }\n }\n}\n"],
5
+ "mappings": ";AAyBO,IAAM,cAAN,MAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAerB,YAAY,OAAO,CAAC,GAAG;AACnB,SAAK,YAAiB,KAAK,aAAa;AACxC,SAAK,eAAiB,KAAK,gBAAgB;AAC3C,SAAK,UAAiB,KAAK,WAAW;AACtC,SAAK,OAAiB,KAAK,SAAS,MAAM,MAAM;AAChD,SAAK,iBAAiB,KAAK,kBAAkB;AAC7C,SAAK,aAAiB,KAAK,eAAe;AAC1C,SAAK,iBAAiB,KAAK,kBAAkB;AAC7C,SAAK,cAAiB,KAAK,eAAe;AAC1C,SAAK,YAAiB,KAAK,aAAa;AAExC,SAAK,cAAe;AACpB,SAAK,eAAe;AACpB,SAAK,gBAAgB,CAAC;AAEtB,SAAK,UAAW,KAAK,QAAQ,KAAK,IAAI;AACtC,SAAK,UAAW,KAAK,QAAQ,KAAK,IAAI;AACtC,SAAK,WAAW,KAAK,SAAS,KAAK,IAAI;AACvC,QAAI,KAAK,WAAW;AAChB,WAAK,UAAU,iBAAiB,YAAa,KAAK,OAAO;AACzD,WAAK,UAAU,iBAAiB,QAAa,KAAK,OAAO;AACzD,WAAK,UAAU,iBAAiB,aAAa,KAAK,QAAQ;AAAA,IAC9D;AAAA,EACJ;AAAA;AAAA;AAAA,EAIA,SAAS;AACL,QAAI,CAAC,KAAK,UAAW;AACrB,eAAW,QAAQ,KAAK,UAAU,iBAAiB,KAAK,YAAY,GAAG;AACnE,UAAI,KAAK,cAAe;AACxB,WAAK,gBAAgB;AACrB,WAAK,aAAa,aAAa,MAAM;AACrC,WAAK,iBAAiB,aAAa,CAAC,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;AAC9D,WAAK,iBAAiB,WAAW,MAAM,KAAK,KAAK,CAAC;AAAA,IACtD;AAAA,EACJ;AAAA;AAAA,EAGA,WAAW;AAAE,WAAO,CAAC,CAAC,KAAK;AAAA,EAAa;AAAA,EAExC,UAAU;AACN,QAAI,KAAK,WAAW;AAChB,WAAK,UAAU,oBAAoB,YAAa,KAAK,OAAO;AAC5D,WAAK,UAAU,oBAAoB,QAAa,KAAK,OAAO;AAC5D,WAAK,UAAU,oBAAoB,aAAa,KAAK,QAAQ;AAAA,IACjE;AACA,SAAK,iBAAiB;AACtB,SAAK,cAAc;AAAA,EACvB;AAAA;AAAA,EAIA,KAAK,IAAI;AACL,QAAI,CAAC,GAAI,QAAO;AAChB,WAAO,GAAG,UAAU,KAAK,OAAO,KACzB,GAAG,eAAe,QAAQ,KAAK,OAAO,EAAE,KACxC;AAAA,EACX;AAAA,EAEA,SAAS;AACL,WAAO,MAAM,KAAK,KAAK,UAAU,iBAAiB,KAAK,YAAY,CAAC;AAAA,EACxE;AAAA,EAEA,YAAY;AACR,WAAO,KAAK,OAAO,EAAE,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,KAAK,IAAI;AAAA,EAC3E;AAAA,EAEA,UAAU,MAAM;AACZ,WAAO,KAAK,SAAS,MACf,KAAK,MAAM,KAAK,SAAS,IACzB,KAAK,OAAO,KAAK,QAAQ;AAAA,EACnC;AAAA,EAEA,OAAO,GAAG,MAAM;AACZ,QAAI,KAAK,kBAAkB,CAAC,KAAK,eAAe,GAAG,IAAI,GAAG;AACtD,QAAE,eAAe;AACjB;AAAA,IACJ;AACA,UAAM,MAAM,KAAK,KAAK,IAAI;AAC1B,SAAK,cAAc;AACnB,SAAK,gBAAgB,KAAK,UAAU;AACpC,SAAK,UAAU,IAAI,UAAU;AAC7B,QAAI;AACA,QAAE,aAAa,gBAAgB;AAC/B,UAAI,OAAO,KAAM,GAAE,aAAa,QAAQ,cAAc,OAAO,GAAG,CAAC;AAAA,IACrE,QAAQ;AAAA,IAAe;AACvB,SAAK,cAAc,GAAG,KAAK,IAAI;AAC/B,SAAK,iBAAiB;AACtB,SAAK,mBAAmB,KAAK,UAAU,KAAK,sBAAsB,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,OAAO;AACH,QAAI,CAAC,KAAK,YAAa;AACvB,SAAK,YAAY,UAAU,OAAO,UAAU;AAC5C,SAAK,cAAc;AACnB,SAAK,iBAAiB;AACtB,SAAK,iBAAiB;AAAA,EAC1B;AAAA,EAEA,QAAQ,GAAG;AACP,QAAI,CAAC,KAAK,YAAa;AACvB,MAAE,eAAe;AACjB,QAAI;AAAE,QAAE,aAAa,aAAa;AAAA,IAAQ,QAAQ;AAAA,IAAe;AACjE,UAAM,QAAQ,KAAK,SAAS,MAAM,EAAE,UAAU,EAAE;AAChD,SAAK,mBAAmB,KAAK;AAC7B,QAAI,KAAK,WAAY,MAAK,gBAAgB,KAAK;AAAA,EACnD;AAAA,EAEA,QAAQ,GAAG;AACP,QAAI,CAAC,KAAK,YAAa;AACvB,MAAE,eAAe;AACjB,UAAM,YAAY,KAAK,iBAAiB;AACxC,QAAI,WAAW,eAAe;AAC1B,WAAK,UAAU,aAAa,KAAK,aAAa,SAAS;AAAA,IAC3D;AACA,SAAK,UAAU;AAAA,EACnB;AAAA,EAEA,SAAS,GAAG;AACR,QAAI,CAAC,KAAK,YAAa;AACvB,QAAI,EAAE,kBAAkB,KAAK,UAAW;AACxC,QAAI,KAAK,UAAU,SAAS,EAAE,aAAa,EAAG;AAC9C,SAAK,UAAU;AAAA,EACnB;AAAA,EAEA,YAAY;AACR,QAAI,KAAK,YAAa,MAAK,YAAY,UAAU,OAAO,UAAU;AAClE,SAAK,iBAAiB;AACtB,SAAK,cAAc;AACnB,SAAK,iBAAiB;AAAA,EAC1B;AAAA,EAEA,mBAAmB;AACf,UAAM,QAAQ,KAAK,UAAU;AAC7B,QAAI,CAAC,MAAM,OAAQ;AACnB,QAAI,KAAK,cAAc,KAAK,IAAG,MAAM,MAAM,KAAK,IAAG,EAAG;AACtD,SAAK,gBAAgB;AACrB,SAAK,YAAY,KAAK;AAAA,EAC1B;AAAA,EAEA,mBAAmB;AACf,QAAI,CAAC,KAAK,cAAc;AACpB,WAAK,eAAe,SAAS,cAAc,KAAK;AAChD,WAAK,aAAa,YAAY,KAAK;AACnC,WAAK,aAAa,aAAa,eAAe,MAAM;AAAA,IACxD;AACA,QAAI,CAAC,KAAK,aAAa,eAAe;AAClC,WAAK,UAAU,YAAY,KAAK,YAAY;AAAA,IAChD;AACA,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,mBAAmB;AAAE,SAAK,cAAc,OAAO;AAAA,EAAG;AAAA,EAElD,mBAAmB,OAAO;AACtB,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,gBAAgB,CAAC,KAAK,YAAa;AAChE,UAAM,QAAQ,KAAK,OAAO,EAAE,OAAO,CAAC,MAAM,MAAM,KAAK,WAAW;AAChE,QAAI,CAAC,MAAM,OAAQ;AACnB,QAAI,OAAO;AACX,QAAI,WAAW;AACf,eAAW,MAAM,OAAO;AACpB,YAAM,SAAS,KAAK,UAAU,GAAG,sBAAsB,CAAC;AACxD,YAAM,OAAO,KAAK,IAAI,QAAQ,MAAM;AACpC,UAAI,OAAO,UAAU;AAAE,mBAAW;AAAM,eAAO,EAAE,IAAI,OAAO;AAAA,MAAG;AAAA,IACnE;AACA,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,SAAS,KAAK;AAC5B,UAAM,MAAM,QAAQ,KAAK,GAAG,qBAAqB,KAAK;AACtD,SAAK,UAAU,aAAa,KAAK,cAAc,OAAO,IAAI;AAAA,EAC9D;AAAA,EAEA,gBAAgB,OAAO;AACnB,QAAI,CAAC,KAAK,UAAW;AACrB,UAAM,OAAO,KAAK,UAAU,sBAAsB;AAClD,UAAM,OAAO;AACb,UAAM,OAAO;AACb,QAAI,KAAK,SAAS,KAAK;AACnB,UAAI,QAAQ,KAAK,MAAM,MAAM;AACzB,aAAK,UAAU,YAAY,KAAK,IAAI,GAAG,KAAK,UAAU,YAAY,IAAI;AAAA,MAC1E,WAAW,KAAK,SAAS,QAAQ,MAAM;AACnC,aAAK,UAAU,YAAY,KAAK;AAAA,UAC5B,KAAK,UAAU;AAAA,UAAc,KAAK,UAAU,YAAY;AAAA,QAAI;AAAA,MACpE;AAAA,IACJ,OAAO;AACH,UAAI,QAAQ,KAAK,OAAO,MAAM;AAC1B,aAAK,UAAU,aAAa,KAAK,IAAI,GAAG,KAAK,UAAU,aAAa,IAAI;AAAA,MAC5E,WAAW,KAAK,QAAQ,QAAQ,MAAM;AAClC,aAAK,UAAU,aAAa,KAAK;AAAA,UAC7B,KAAK,UAAU;AAAA,UAAa,KAAK,UAAU,aAAa;AAAA,QAAI;AAAA,MACpE;AAAA,IACJ;AAAA,EACJ;AACJ;",
6
+ "names": []
7
+ }