flexdesk 0.17.7 → 0.17.9

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 (284) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +0 -0
  3. package/README.md +0 -0
  4. package/THIRD_PARTY_LICENSES.md +0 -0
  5. package/canvas.js +0 -0
  6. package/charts.js +0 -0
  7. package/core.js +0 -0
  8. package/css/base.css +21 -1
  9. package/css/flexdesk.css +30 -3
  10. package/css/overrides.css +0 -0
  11. package/css/reset.css +0 -0
  12. package/dist/canvas.js +0 -0
  13. package/dist/canvas.js.map +0 -0
  14. package/dist/charts.js +7 -6
  15. package/dist/charts.js.map +2 -2
  16. package/dist/{chunk-RMTRWWEO.js → chunk-2YTM3GJE.js} +67 -26
  17. package/dist/chunk-2YTM3GJE.js.map +7 -0
  18. package/dist/chunk-3PHCPZHT.js +0 -0
  19. package/dist/chunk-3PHCPZHT.js.map +0 -0
  20. package/dist/{chunk-UURZ37SD.js → chunk-7U4IB65H.js} +27 -9
  21. package/dist/chunk-7U4IB65H.js.map +7 -0
  22. package/dist/{chunk-D3COLZZ2.js → chunk-BE2PU3LL.js} +2 -2
  23. package/dist/{chunk-D3COLZZ2.js.map → chunk-BE2PU3LL.js.map} +0 -0
  24. package/dist/chunk-CVMCZZX2.js +0 -0
  25. package/dist/chunk-CVMCZZX2.js.map +0 -0
  26. package/dist/{chunk-UWO43BZU.js → chunk-F35XSQWA.js} +16 -20
  27. package/dist/chunk-F35XSQWA.js.map +7 -0
  28. package/dist/chunk-FOOS3T5L.js +0 -0
  29. package/dist/chunk-FOOS3T5L.js.map +0 -0
  30. package/dist/chunk-GOQLR5WR.js +0 -0
  31. package/dist/chunk-GOQLR5WR.js.map +0 -0
  32. package/dist/chunk-HNU6MM2B.js +0 -0
  33. package/dist/chunk-HNU6MM2B.js.map +0 -0
  34. package/dist/chunk-JKZLNKG6.js +0 -0
  35. package/dist/chunk-JKZLNKG6.js.map +0 -0
  36. package/dist/chunk-JYWURG5T.js +0 -0
  37. package/dist/chunk-JYWURG5T.js.map +0 -0
  38. package/dist/{chunk-RKNFBX5K.js → chunk-MIW7D6WC.js} +16 -4
  39. package/dist/{chunk-RKNFBX5K.js.map → chunk-MIW7D6WC.js.map} +2 -2
  40. package/dist/chunk-N7HC3ADV.js +0 -0
  41. package/dist/chunk-N7HC3ADV.js.map +0 -0
  42. package/dist/{chunk-LWV23YVE.js → chunk-NIJEJAZ7.js} +42 -18
  43. package/dist/chunk-NIJEJAZ7.js.map +7 -0
  44. package/dist/{chunk-HYJYYEPK.js → chunk-REJ5HLIM.js} +2 -2
  45. package/dist/{chunk-HYJYYEPK.js.map → chunk-REJ5HLIM.js.map} +0 -0
  46. package/dist/{chunk-WVFGV5FT.js → chunk-WUFLGETN.js} +39 -2
  47. package/dist/{chunk-WVFGV5FT.js.map → chunk-WUFLGETN.js.map} +3 -3
  48. package/dist/core.js +0 -0
  49. package/dist/core.js.map +0 -0
  50. package/dist/editor.js +2 -2
  51. package/dist/editor.js.map +0 -0
  52. package/dist/flexdesk.css +30 -3
  53. package/dist/flow.js +3 -3
  54. package/dist/flow.js.map +0 -0
  55. package/dist/host.js +0 -0
  56. package/dist/host.js.map +0 -0
  57. package/dist/reset.css +0 -0
  58. package/dist/tile_registry-6WZPTUZV.js +0 -0
  59. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  60. package/dist/tiles.js +63 -20
  61. package/dist/tiles.js.map +2 -2
  62. package/dist/widgets.js +6 -6
  63. package/dist/widgets.js.map +0 -0
  64. package/dist/wm.js +5 -5
  65. package/dist/wm.js.map +0 -0
  66. package/docs/README.md +0 -0
  67. package/docs/best-practices.md +0 -0
  68. package/docs/components/charts.md +95 -0
  69. package/docs/components/command-palette.md +0 -0
  70. package/docs/components/conversation.md +0 -0
  71. package/docs/components/data-table-grid.md +0 -0
  72. package/docs/components/data-table.md +0 -0
  73. package/docs/components/editor.md +0 -0
  74. package/docs/components/flow-canvas-editor.md +0 -0
  75. package/docs/components/flow-editors.md +0 -0
  76. package/docs/components/flow-lane-editor.md +0 -0
  77. package/docs/components/flow-outline-editor.md +0 -0
  78. package/docs/components/forms-and-dialogs.md +0 -0
  79. package/docs/components/input-fields.md +0 -0
  80. package/docs/components/keymap.md +0 -0
  81. package/docs/components/markdown-and-outline.md +0 -0
  82. package/docs/components/menus.md +41 -0
  83. package/docs/components/notifications.md +0 -0
  84. package/docs/components/panels-and-desktops.md +0 -0
  85. package/docs/components/record-list-and-fact-table.md +0 -0
  86. package/docs/components/settings-view.md +0 -0
  87. package/docs/components/shell.md +0 -0
  88. package/docs/components/slide-out-panel.md +0 -0
  89. package/docs/components/tiles.md +25 -0
  90. package/docs/components/wizard.md +0 -0
  91. package/docs/concepts/building-and-packaging.md +0 -0
  92. package/docs/concepts/domain-neutrality.md +0 -0
  93. package/docs/concepts/host-port.md +0 -0
  94. package/docs/concepts/packages-and-entry-points.md +0 -0
  95. package/docs/concepts/peer-dependencies.md +0 -0
  96. package/docs/concepts/state-guard.md +0 -0
  97. package/docs/concepts/styling.md +0 -0
  98. package/docs/quickstart.md +0 -0
  99. package/editor.js +0 -0
  100. package/flow.js +0 -0
  101. package/host.js +0 -0
  102. package/package.json +1 -1
  103. package/src/canvas/canvas_adapter.js +0 -0
  104. package/src/canvas/edge_router.js +0 -0
  105. package/src/canvas/graph/connector_router.js +0 -0
  106. package/src/canvas/graph/history_service.js +0 -0
  107. package/src/canvas/graph/node_platform.js +0 -0
  108. package/src/canvas/index.js +0 -0
  109. package/src/canvas/layered_layout.js +0 -0
  110. package/src/charts/chart_types.js +3 -2
  111. package/src/charts/data_series_plot_window.js +0 -0
  112. package/src/charts/downsample.js +0 -0
  113. package/src/charts/plot_config.js +0 -0
  114. package/src/charts/plot_config_editor.js +8 -2
  115. package/src/charts/plot_grid.js +16 -4
  116. package/src/charts/plot_popout_window.js +2 -1
  117. package/src/charts/plotly_wrapper.js +0 -0
  118. package/src/charts/series_transforms.js +114 -20
  119. package/src/core/event_bus.js +0 -0
  120. package/src/core/logging.js +0 -0
  121. package/src/core/settings.js +0 -0
  122. package/src/core/state_guard.js +0 -0
  123. package/src/core/state_guard_config.js +0 -0
  124. package/src/core/state_machine.js +0 -0
  125. package/src/editor/editor_pane.js +0 -0
  126. package/src/editor/monaco_editor_factory.js +0 -0
  127. package/src/editor/monaco_loader.js +0 -0
  128. package/src/editor/notebook_search.js +0 -0
  129. package/src/editor/notebook_tab_bar.js +26 -7
  130. package/src/editor/notebook_undo_manager.js +0 -0
  131. package/src/editor/split_pane_container.js +0 -0
  132. package/src/flow/canvas_editor/connections.js +0 -0
  133. package/src/flow/canvas_editor/editor.js +0 -0
  134. package/src/flow/canvas_editor/index.js +0 -0
  135. package/src/flow/canvas_editor/layout.js +0 -0
  136. package/src/flow/canvas_editor/strings.js +0 -0
  137. package/src/flow/kit/always_before.js +0 -0
  138. package/src/flow/kit/catalogue.js +0 -0
  139. package/src/flow/kit/chip_input.js +0 -0
  140. package/src/flow/kit/dom.js +0 -0
  141. package/src/flow/kit/findings.js +0 -0
  142. package/src/flow/kit/history.js +0 -0
  143. package/src/flow/kit/index.js +0 -0
  144. package/src/flow/kit/keys.js +0 -0
  145. package/src/flow/kit/logic_graph.js +0 -0
  146. package/src/flow/kit/references.js +0 -0
  147. package/src/flow/kit/settings_panel.js +0 -0
  148. package/src/flow/kit/settings_schema.js +0 -0
  149. package/src/flow/kit/step_picker.js +0 -0
  150. package/src/flow/kit/strings.js +0 -0
  151. package/src/flow/kit/value_picker.js +0 -0
  152. package/src/flow/kit/widgets.js +0 -0
  153. package/src/flow/lanes/editor.js +0 -0
  154. package/src/flow/lanes/index.js +0 -0
  155. package/src/flow/lanes/layout.js +0 -0
  156. package/src/flow/lanes/pipeline.js +0 -0
  157. package/src/flow/lanes/preview.js +0 -0
  158. package/src/flow/lanes/strings.js +0 -0
  159. package/src/flow/outline/editor.js +0 -0
  160. package/src/flow/outline/index.js +0 -0
  161. package/src/flow/outline/mapping.js +0 -0
  162. package/src/flow/outline/menu.js +0 -0
  163. package/src/flow/outline/operations.js +0 -0
  164. package/src/flow/outline/recognise.js +0 -0
  165. package/src/flow/outline/strings.js +0 -0
  166. package/src/help/help_modal.js +0 -0
  167. package/src/help/help_registry.js +0 -0
  168. package/src/host/external_links.js +0 -0
  169. package/src/host/file_picker.js +0 -0
  170. package/src/host/host.js +0 -0
  171. package/src/host/photino_host.js +0 -0
  172. package/src/host/photino_host.md +0 -0
  173. package/src/host/pywebview_host.js +0 -0
  174. package/src/tiles/chart_tiles.js +0 -0
  175. package/src/tiles/config_schema.js +0 -0
  176. package/src/tiles/figure_tile.js +0 -0
  177. package/src/tiles/layout_persistence.js +0 -0
  178. package/src/tiles/list_tiles.js +0 -0
  179. package/src/tiles/scope_picker.js +0 -0
  180. package/src/tiles/tile_base.js +67 -16
  181. package/src/tiles/tile_config_form.js +0 -0
  182. package/src/tiles/tile_grid.js +0 -0
  183. package/src/tiles/tile_registry.js +0 -0
  184. package/src/tiling/bottom_panel_tabs.js +0 -0
  185. package/src/tiling/command_palette.js +0 -0
  186. package/src/tiling/content_registry.js +0 -0
  187. package/src/tiling/desktops.js +0 -0
  188. package/src/tiling/entity_sources.js +0 -0
  189. package/src/tiling/keymap.js +0 -0
  190. package/src/tiling/kind_taxonomy.js +0 -0
  191. package/src/tiling/landing_table.js +0 -0
  192. package/src/tiling/loading_overlay.js +0 -0
  193. package/src/tiling/nav_panel.js +0 -0
  194. package/src/tiling/page_factory.js +0 -0
  195. package/src/tiling/panel_availability.js +0 -0
  196. package/src/tiling/panel_keys.js +0 -0
  197. package/src/tiling/shell.js +0 -0
  198. package/src/tiling/tab_strip.js +0 -0
  199. package/src/tiling/tile_breadcrumb.js +0 -0
  200. package/src/tiling/tile_renderer.js +0 -0
  201. package/src/tiling/tile_tab_menu.js +0 -0
  202. package/src/tiling/tile_tree.js +0 -0
  203. package/src/tiling/wm.js +0 -0
  204. package/src/tiling/zoom.js +0 -0
  205. package/src/ui/base/component_base.js +0 -0
  206. package/src/ui/base/controller_base.js +0 -0
  207. package/src/ui/base/page_base.js +0 -0
  208. package/src/ui/components/about_dialog.js +0 -0
  209. package/src/ui/components/action_dropdown.js +19 -6
  210. package/src/ui/components/alert_log.js +0 -0
  211. package/src/ui/components/alerts_pane.js +0 -0
  212. package/src/ui/components/attribute_list_editor.js +0 -0
  213. package/src/ui/components/autocomplete_field.js +0 -0
  214. package/src/ui/components/cell_editor_host.js +0 -0
  215. package/src/ui/components/cell_editors.js +0 -0
  216. package/src/ui/components/cell_grid.js +0 -0
  217. package/src/ui/components/cell_paste.js +0 -0
  218. package/src/ui/components/cell_selection.js +0 -0
  219. package/src/ui/components/choice_controls.js +0 -0
  220. package/src/ui/components/color_field.js +0 -0
  221. package/src/ui/components/color_list_field.js +0 -0
  222. package/src/ui/components/column_fit.js +0 -0
  223. package/src/ui/components/column_groups.js +0 -0
  224. package/src/ui/components/column_order.js +0 -0
  225. package/src/ui/components/computing_status_window.js +0 -0
  226. package/src/ui/components/confirm_dialog.js +0 -0
  227. package/src/ui/components/context_menu.js +20 -20
  228. package/src/ui/components/conversation_view.js +0 -0
  229. package/src/ui/components/crosstab.js +0 -0
  230. package/src/ui/components/data_table.js +45 -19
  231. package/src/ui/components/detail_header.js +0 -0
  232. package/src/ui/components/document_outline.js +0 -0
  233. package/src/ui/components/drag_reorder.js +0 -0
  234. package/src/ui/components/fact_table.js +0 -0
  235. package/src/ui/components/fill_handle.js +0 -0
  236. package/src/ui/components/gallery_picker.js +0 -0
  237. package/src/ui/components/header_actions.js +0 -0
  238. package/src/ui/components/inline_renamer.js +0 -0
  239. package/src/ui/components/job_progress.js +0 -0
  240. package/src/ui/components/managed_window.js +0 -0
  241. package/src/ui/components/menu_bar.js +0 -0
  242. package/src/ui/components/modal.js +0 -0
  243. package/src/ui/components/notification_history.js +0 -0
  244. package/src/ui/components/progress.js +0 -0
  245. package/src/ui/components/record_list.js +0 -0
  246. package/src/ui/components/row_activation.js +0 -0
  247. package/src/ui/components/section_stack.js +0 -0
  248. package/src/ui/components/series.js +0 -0
  249. package/src/ui/components/settings_view.js +0 -0
  250. package/src/ui/components/slide_out_panel.js +0 -0
  251. package/src/ui/components/slider_field.js +0 -0
  252. package/src/ui/components/sortable_list.js +0 -0
  253. package/src/ui/components/staged_edits.js +0 -0
  254. package/src/ui/components/staged_history.js +0 -0
  255. package/src/ui/components/table_filter_chips.js +0 -0
  256. package/src/ui/components/table_state_store.js +0 -0
  257. package/src/ui/components/table_structure.js +0 -0
  258. package/src/ui/components/table_values.js +0 -0
  259. package/src/ui/components/tabs.js +0 -0
  260. package/src/ui/components/tag_input.js +0 -0
  261. package/src/ui/components/toast.js +0 -0
  262. package/src/ui/components/toggle_field.js +0 -0
  263. package/src/ui/components/tree_view.js +0 -0
  264. package/src/ui/components/tsv.js +0 -0
  265. package/src/ui/components/window_taskbar.js +0 -0
  266. package/src/ui/components/wizard.js +0 -0
  267. package/src/ui/controllers/window_chrome_controller.js +0 -0
  268. package/src/ui/notification_center.js +0 -0
  269. package/src/ui/utils/auto_scrollbars.js +0 -0
  270. package/src/ui/utils/markdown.js +0 -0
  271. package/src/ui/utils/menu_viewport.js +81 -0
  272. package/src/ui/utils/native_context_menu.js +0 -0
  273. package/src/ui/utils/overlay_scrollbar.js +0 -0
  274. package/src/ui/utils/raf_resize_observer.js +0 -0
  275. package/src/ui/utils/scroll_lock.js +0 -0
  276. package/src/ui/utils/strip_scroll.js +0 -0
  277. package/src/ui/utils/tooltip_service.js +0 -0
  278. package/tiles.js +0 -0
  279. package/widgets.js +0 -0
  280. package/wm.js +0 -0
  281. package/dist/chunk-LWV23YVE.js.map +0 -7
  282. package/dist/chunk-RMTRWWEO.js.map +0 -7
  283. package/dist/chunk-UURZ37SD.js.map +0 -7
  284. package/dist/chunk-UWO43BZU.js.map +0 -7
@@ -3,8 +3,11 @@ import {
3
3
  wheelStripSideways
4
4
  } from "./chunk-N7HC3ADV.js";
5
5
  import {
6
- DragReorder
7
- } from "./chunk-WVFGV5FT.js";
6
+ DragReorder,
7
+ focusMenuItem,
8
+ moveMenuFocus,
9
+ placeMenuInViewport
10
+ } from "./chunk-WUFLGETN.js";
8
11
 
9
12
  // src/editor/notebook_tab_bar.js
10
13
  var DEFAULT_TAB_DRAG_MIME = "application/x-flexdesk-tab";
@@ -27,6 +30,8 @@ var NotebookTabBar = class _NotebookTabBar {
27
30
  // ─── Context menu ────────────────────────────────────────────────────────
28
31
  /** @type {HTMLElement|null} */
29
32
  #contextMenuEl = null;
33
+ /** BUG-0040. What had focus when the menu opened. @type {Element|null} */
34
+ #contextMenuReturnFocus = null;
30
35
  /** @type {Function|null} */
31
36
  #boundHideContextMenu = null;
32
37
  // ─── Drag and drop ───────────────────────────────────────────────────────
@@ -305,6 +310,7 @@ var NotebookTabBar = class _NotebookTabBar {
305
310
  while (cleaned.length > 0 && cleaned[cleaned.length - 1].separator) cleaned.pop();
306
311
  const menu = document.createElement("div");
307
312
  menu.className = "nb-context-menu";
313
+ menu.setAttribute("role", "menu");
308
314
  for (const item of cleaned) {
309
315
  if (item.separator) {
310
316
  const sep = document.createElement("div");
@@ -313,6 +319,8 @@ var NotebookTabBar = class _NotebookTabBar {
313
319
  continue;
314
320
  }
315
321
  const btn = document.createElement("button");
322
+ btn.type = "button";
323
+ btn.setAttribute("role", "menuitem");
316
324
  btn.className = "nb-context-menu__item";
317
325
  if (item.danger) btn.classList.add("nb-context-menu__item--danger");
318
326
  if (item.disabled) btn.classList.add("nb-context-menu__item--disabled");
@@ -326,13 +334,11 @@ var NotebookTabBar = class _NotebookTabBar {
326
334
  }
327
335
  menu.appendChild(btn);
328
336
  }
337
+ this.#contextMenuReturnFocus = document.activeElement;
329
338
  document.body.appendChild(menu);
330
339
  this.#contextMenuEl = menu;
331
- const rect = menu.getBoundingClientRect();
332
- const left = Math.min(x, window.innerWidth - rect.width - 8);
333
- const top = Math.min(y, window.innerHeight - rect.height - 8);
334
- menu.style.left = `${Math.max(0, left)}px`;
335
- menu.style.top = `${Math.max(0, top)}px`;
340
+ placeMenuInViewport(menu, x, y);
341
+ focusMenuItem(this.#contextMenuItems()[0]);
336
342
  requestAnimationFrame(() => menu.classList.add("visible"));
337
343
  setTimeout(() => {
338
344
  document.addEventListener("click", this.#boundHideContextMenu, true);
@@ -342,14 +348,26 @@ var NotebookTabBar = class _NotebookTabBar {
342
348
  }
343
349
  #hideContextMenu() {
344
350
  if (!this.#contextMenuEl) return;
351
+ const returnTo = this.#contextMenuReturnFocus;
352
+ const held = this.#contextMenuEl.contains(document.activeElement);
345
353
  this.#contextMenuEl.remove();
346
354
  this.#contextMenuEl = null;
355
+ this.#contextMenuReturnFocus = null;
347
356
  document.removeEventListener("click", this.#boundHideContextMenu, true);
348
357
  document.removeEventListener("contextmenu", this.#boundHideContextMenu, true);
349
358
  document.removeEventListener("keydown", this.#onKeyDown);
359
+ if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });
360
+ }
361
+ /** BUG-0040. The menu's items the keyboard can land on. */
362
+ #contextMenuItems() {
363
+ return this.#contextMenuEl ? [...this.#contextMenuEl.querySelectorAll(".nb-context-menu__item:not(.nb-context-menu__item--disabled)")] : [];
350
364
  }
351
365
  #onKeyDown = (e) => {
352
- if (e.key === "Escape") this.#hideContextMenu();
366
+ if (e.key === "Escape") {
367
+ this.#hideContextMenu();
368
+ return;
369
+ }
370
+ moveMenuFocus(e, this.#contextMenuItems());
353
371
  };
354
372
  #handleContextMenuAction(action, filePath) {
355
373
  switch (action) {
@@ -456,4 +474,4 @@ export {
456
474
  getTabDragMime,
457
475
  NotebookTabBar
458
476
  };
459
- //# sourceMappingURL=chunk-UURZ37SD.js.map
477
+ //# sourceMappingURL=chunk-7U4IB65H.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/editor/notebook_tab_bar.js"],
4
+ "sourcesContent": ["/**\n * NotebookTabBar \u2014 tab strip showing open files, with dirty indicators,\n * close buttons, drag-and-drop reordering, double-click rename, and\n * right-click context menu.\n *\n * Uses the same `.tab` / `.tab-title` / `.tab-dirty` / `.tab-close` DOM structure\n * as the flow-page tab bar, so all editor tab bars share a unified appearance.\n *\n * Calls back into parent via:\n * onActivate(filePath)\n * onClose(filePath)\n * onCloseOthers(filePath)\n * onCloseToRight(filePath)\n * onCloseAll()\n * onCloseSaved()\n * onRename(oldPath, newPath)\n * onDuplicate(filePath)\n * onReorder(orderedPaths)\n * onRevealInExplorer(filePath)\n * onSplitRight(filePath)\n * onMoveToOtherPane(filePath)\n * onDropFromOtherPane(filePath) \u2014 cross-pane drag-and-drop\n */\n\nimport { DragReorder } from '../ui/components/drag_reorder.js';\nimport { revealInStrip, wheelStripSideways } from '../ui/utils/strip_scroll.js';\nimport { placeMenuInViewport, moveMenuFocus, focusMenuItem } from '../ui/utils/menu_viewport.js';\n\n/** The MIME type a dragged editor tab carries when the embedder names none. */\nexport const DEFAULT_TAB_DRAG_MIME = 'application/x-flexdesk-tab';\n\n/** Custom MIME type so tab bars and editor panes recognise cross-pane drags. */\nlet _tabMime = DEFAULT_TAB_DRAG_MIME;\n\n/**\n * Name the MIME type editor tabs carry while dragged. Every NotebookTabBar and\n * EditorPane in the document reads it at drag time, so they always agree.\n * Call at boot, before the first drag.\n *\n * @param {string} [mime=DEFAULT_TAB_DRAG_MIME]\n */\nexport function setTabDragMime(mime = DEFAULT_TAB_DRAG_MIME) {\n _tabMime = (typeof mime === 'string' && mime) ? mime : DEFAULT_TAB_DRAG_MIME;\n}\n\n/** @returns {string} The MIME type editor tabs carry while dragged. */\nexport function getTabDragMime() {\n return _tabMime;\n}\n\nexport class NotebookTabBar {\n /** @type {HTMLElement} */\n #container = null;\n\n /** @type {object[]} [{filePath, label, fileType, isDirty}] */\n #tabs = [];\n\n /** @type {string|null} */\n #activeTab = null;\n\n /** @type {object} callbacks */\n #callbacks = {};\n\n // \u2500\u2500\u2500 Context menu \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\u2500\u2500\u2500\u2500\u2500\n\n /** @type {HTMLElement|null} */\n #contextMenuEl = null;\n /** BUG-0040. What had focus when the menu opened. @type {Element|null} */\n #contextMenuReturnFocus = null;\n\n /** @type {Function|null} */\n #boundHideContextMenu = null;\n\n // \u2500\u2500\u2500 Drag and drop \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\u2500\u2500\u2500\u2500\n\n /** Same-pane reorder. Cross-pane drops stay in this class's own\n * container handlers (#onStripDrag*).\n * @type {DragReorder|null} */\n #dragReorder = null;\n\n mount(container, callbacks = {}) {\n this.#container = container;\n this.#callbacks = callbacks;\n this.#container.className = 'tabs notebook-tabs';\n this.#boundHideContextMenu = () => this.#hideContextMenu();\n this.#installStripDnDHandlers();\n // NOT PASSIVE: it has to be able to take the wheel from the page.\n this.#container.addEventListener('wheel', this.#onWheel, { passive: false });\n this.#dragReorder = new DragReorder({\n container: this.#container,\n itemSelector: '.tab',\n keyAttr: 'path',\n axis: 'x',\n draggableGuard: (e) => {\n if (e.target.closest('.twm-tab-close')) return false;\n const title = e.target.closest('.tab')?.querySelector('.twm-tab-title');\n return title?.getAttribute('contenteditable') !== 'true';\n },\n onDragStart: (e, key, item) => {\n // Carry the cross-pane MIME so other panes accept this tab.\n try { e.dataTransfer.setData(_tabMime, key); } catch (_) {}\n // C33. \u2026AND LET THE HOST ADD ITS OWN, which is the whole of\n // what a second consumer of this strip needs from it.\n //\n // `DragReorder` publishes this hook for exactly this purpose\n // (\"host hook to augment dataTransfer (e.g. add a cross-pane\n // MIME)\", `../ui/components/drag_reorder.js`), and this class\n // consumed it and stopped there. The tile renderer mounts one\n // of these per tile and needs to mark the drag as one of ITS\n // tabs \u2014 a different question from \"is this an editor tab\",\n // asked of the same gesture.\n //\n // ADDITIVE BY CONSTRUCTION: the tab MIME is set first and\n // unchanged, so `EditorPane`'s existing cross-pane drop cannot\n // notice that anyone else is listening.\n try { this.#callbacks.onDragStart?.(e, key, item); }\n catch (err) { console.error('[tab-bar] onDragStart threw', err); }\n },\n onReorder: (order) => this.#callbacks.onReorder?.(order),\n });\n this.#render();\n }\n\n /**\n * Sync tabs from ProjectModel state.\n * @param {Array<{ filePath: string, fileType: string, isDirty: boolean }>} tabs\n * @param {string|null} activeFilePath\n */\n update(tabs, activeFilePath) {\n this.#tabs = tabs;\n this.#activeTab = activeFilePath;\n this.#render();\n }\n\n markDirty(filePath, isDirty) {\n const tab = this.#tabs.find(t => t.filePath === filePath);\n if (tab) {\n tab.isDirty = isDirty;\n this.#render();\n }\n }\n\n dispose() {\n this.#hideContextMenu();\n this.#removeStripDnDHandlers();\n this.#container?.removeEventListener('wheel', this.#onWheel);\n if (this.#revealFrame) cancelAnimationFrame(this.#revealFrame);\n this.#revealFrame = 0;\n this.#dragReorder?.destroy();\n this.#dragReorder = null;\n this.#container = null;\n }\n\n // Material symbol per file kind, merged over the generic set. The map used to\n // hardcode EcoAgent's kinds \u2014 `scenario`, `dashboard`, `model` \u2014 so a generic\n // tab bar knew one application's file taxonomy. Pass `fileTypeIcons` to add\n // your own.\n static #FILE_TYPE_ICONS = {\n module: 'extension',\n test: 'fact_check',\n diff: 'difference',\n unknown: 'description',\n };\n\n static #EXTRA_ICONS = {};\n\n /**\n * Add your application's file kinds to the tab-bar icon map.\n *\n * The map used to hardcode `scenario: 'science'`, `dashboard: 'dashboard'` and\n * `model: 'schema'` \u2014 EcoAgent's file taxonomy, baked into a generic tab bar.\n *\n * @param {Object.<string, string>} icons fileType -> material symbol name\n */\n static setFileTypeIcons(icons = {}) {\n NotebookTabBar.#EXTRA_ICONS = { ...icons };\n }\n\n static #iconFor(fileType) {\n return NotebookTabBar.#EXTRA_ICONS[fileType]\n ?? NotebookTabBar.#FILE_TYPE_ICONS[fileType]\n ?? NotebookTabBar.#FILE_TYPE_ICONS.unknown;\n }\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n // Rendering\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n #render() {\n if (!this.#container) return;\n\n // THE STRIP KEEPS ITS SCROLL ACROSS A REBUILD. Emptying it shrinks its\n // scroll width to nothing, which clamps `scrollLeft` to 0 \u2014 so every\n // render (a tab activated, a title changed) threw a strip scrolled to\n // its tenth tab back to its first.\n const keepLeft = this.#container.scrollLeft;\n this.#container.innerHTML = '';\n\n for (const tab of this.#tabs) {\n const isActive = tab.filePath === this.#activeTab;\n const rawPath = tab.filePath.replace(/^(diff|builtin):\\/\\//, '');\n const label = tab.label\n ?? (tab.fileType === 'diff'\n ? `\u2194 ${rawPath.split('/').pop()}`\n : rawPath.split('/').pop().replace(/\\.[^.]+$/, ''));\n const icon = NotebookTabBar.#iconFor(tab.fileType);\n\n const tabEl = document.createElement('div');\n tabEl.className = `tab${isActive ? ' active' : ''}${tab.isPreview ? ' tab--preview' : ''}`;\n tabEl.dataset.path = tab.filePath;\n tabEl.title = tab.filePath;\n\n tabEl.innerHTML = `\n <span class=\"material-symbols-outlined tab-icon\">${icon}</span>\n <span class=\"twm-tab-title\">${this.#escHtml(label)}</span>\n ${tab.isDirty ? '<span class=\"tab-dirty\">\\u25cf</span>' : ''}\n ${tab.readonly ? '' : '<button class=\"twm-tab-close\" title=\"Close tab\" aria-label=\"Close tab\">\\u00d7</button>'}\n `;\n\n // Click \u2192 activate\n tabEl.addEventListener('click', (e) => {\n if (e.target.closest('.twm-tab-close')) return;\n if (tabEl.querySelector('.twm-tab-title')?.getAttribute('contenteditable') === 'true') return;\n this.#callbacks.onActivate?.(tab.filePath);\n });\n\n // Close button\n tabEl.querySelector('.twm-tab-close')?.addEventListener('click', (e) => {\n e.stopPropagation();\n this.#callbacks.onClose?.(tab.filePath);\n });\n\n // Double-click \u2192 inline rename\n tabEl.addEventListener('dblclick', (e) => {\n if (e.target.closest('.twm-tab-close')) return;\n this.#beginInlineRename(tab.filePath);\n });\n\n // F2 \u2192 inline rename\n tabEl.addEventListener('keydown', (e) => {\n if (e.key === 'F2') {\n e.preventDefault();\n this.#beginInlineRename(tab.filePath);\n }\n });\n\n // Context menu\n tabEl.addEventListener('contextmenu', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this.#showContextMenu(e.clientX, e.clientY, tab.filePath);\n });\n\n this.#container.appendChild(tabEl);\n }\n // Make the freshly-rendered tabs draggable-to-reorder.\n this.#dragReorder?.attach();\n\n this.#container.scrollLeft = keepLeft;\n // A tab that has just BECOME active \u2014 opened, switched to, moved here\n // from another pane \u2014 is scrolled into the strip. Only then: a render\n // for any other reason (a title, a dirty dot) leaves the strip where the\n // reader put it, rather than yanking it back to the active tab.\n const revealKey = `${this.#activeTab}\\u0000${this.#tabs.length}`;\n if (revealKey !== this.#revealedKey) {\n this.#revealedKey = revealKey;\n this.#revealActive(true);\n }\n }\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n // Scrolling (1.5) \u2014 the strip scrolls rather than wrapping or shrinking its\n // tabs, and hides its scrollbar (a bar in a 26px strip eats a third of every\n // label). So it owes a mouse user every other way in: a plain wheel, the\n // active tab brought into view, and \u2014 in `DragReorder._autoScrollEdge` \u2014\n // the edges scrolling while a tab is dragged toward them.\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n /** @type {string|null} the active tab + count last scrolled into view */\n #revealedKey = null;\n\n /** @type {number} a pending retry, for a strip not yet laid out */\n #revealFrame = 0;\n\n /** Bring the active tab fully into the strip (`revealInStrip`). */\n #revealActive(retry = false) {\n const el = this.#container;\n if (!el) return;\n const active = el.querySelector(':scope > .tab.active');\n if (!active) return;\n // Mounted but not laid out yet (the renderer updates a strip before it\n // attaches it). Once, on the next frame \u2014 not a loop.\n if (!revealInStrip(el, active) && retry && !this.#revealFrame\n && typeof requestAnimationFrame === 'function') {\n this.#revealFrame = requestAnimationFrame(() => {\n this.#revealFrame = 0;\n this.#revealActive(false);\n });\n }\n }\n\n /** A vertical wheel scrolls the strip sideways (`wheelStripSideways`). */\n #onWheel = (e) => wheelStripSideways(this.#container, e);\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n // Drag and drop reordering (same-pane + cross-pane)\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n #installStripDnDHandlers() {\n if (!this.#container) return;\n this.#container.addEventListener('dragover', this.#onStripDragOver);\n this.#container.addEventListener('drop', this.#onStripDrop);\n this.#container.addEventListener('dragleave', this.#onStripDragLeave);\n }\n\n #removeStripDnDHandlers() {\n if (!this.#container) return;\n this.#container.removeEventListener('dragover', this.#onStripDragOver);\n this.#container.removeEventListener('drop', this.#onStripDrop);\n this.#container.removeEventListener('dragleave', this.#onStripDragLeave);\n }\n\n /** Check whether a drag event carries a cross-pane tab (not from this bar).\n *\n * C33. A HOST MAY NAME A SECOND MIME, at mount, as\n * `callbacks.externalTabMimes` \u2014 the tile renderer's tabs are not editor\n * tabs and do not carry the editor tab MIME (getTabDragMime()), so without this the\n * strip in tile B would refuse a tab dragged out of tile A's strip. Empty\n * by default, so every existing consumer keeps exactly today's admission\n * test.\n *\n * THE `isActive()` EARLY RETURN STAYS FIRST AND STAYS UNCHANGED. It is the\n * one line that keeps this bar's own reorder from being read as a foreign\n * drop, and no widening of the MIME list may be allowed to reach past it.\n *\n * Only `types` is consulted, never `getData` \u2014 under the HTML5 protected\n * mode `getData` returns the empty string for every event except `drop`,\n * so a payload read here would find nothing and arm nothing. */\n #isExternalTabDrag(e) {\n if (this.#dragReorder?.isActive()) return false; // same-pane drag is active\n const extra = Array.isArray(this.#callbacks.externalTabMimes)\n ? this.#callbacks.externalTabMimes : [];\n try {\n const types = e.dataTransfer.types;\n return types.includes(_tabMime) || extra.some((m) => types.includes(m));\n } catch { return false; }\n }\n\n // Same-pane reorder is handled by the shared DragReorder module (set\n // up in mount()). These container handlers cover ONLY cross-pane tab\n // drops \u2014 gated on `#isExternalTabDrag`, which defers while our own\n // reorder drag is active.\n\n #onStripDragOver = (e) => {\n if (this.#isExternalTabDrag(e)) {\n e.preventDefault();\n try { e.dataTransfer.dropEffect = 'move'; } catch (_) {}\n this.#container.classList.add('notebook-tabs--drop-target');\n }\n };\n\n #onStripDrop = (e) => {\n if (this.#isExternalTabDrag(e)) {\n e.preventDefault();\n this.#container.classList.remove('notebook-tabs--drop-target');\n const filePath = e.dataTransfer.getData(_tabMime) || e.dataTransfer.getData('text/plain');\n if (filePath) {\n this.#callbacks.onDropFromOtherPane?.(filePath);\n }\n }\n };\n\n #onStripDragLeave = (e) => {\n if (!this.#container.contains(e.relatedTarget)) {\n this.#container.classList.remove('notebook-tabs--drop-target');\n }\n };\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n // Context menu\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n #showContextMenu(x, y, filePath) {\n this.#hideContextMenu();\n\n const idx = this.#tabs.findIndex(t => t.filePath === filePath);\n const total = this.#tabs.length;\n\n const allItems = [\n { label: 'Rename', icon: 'edit', action: 'rename', shortcut: 'F2' },\n { label: 'Duplicate', icon: 'content_copy', action: 'duplicate' },\n { separator: true },\n { label: 'Split Right', icon: 'vertical_split', action: 'split-right', shortcut: 'Ctrl+\\\\' },\n { label: 'Move to Other Pane',icon: 'swap_horiz', action: 'move-to-other' },\n { separator: true },\n { label: 'Close', icon: 'close', action: 'close', danger: true },\n { label: 'Close Others', icon: 'close_fullscreen', action: 'close-others', danger: true, disabled: total <= 1 },\n { label: 'Close to the Right',icon: 'tab_close', action: 'close-right', danger: true, disabled: idx === -1 || idx >= total - 1 },\n { label: 'Close All', icon: 'tab_close_inactive', action: 'close-all', danger: true },\n { label: 'Close Saved', icon: 'check_circle', action: 'close-saved', danger: true },\n { separator: true },\n { label: 'Reveal in Explorer', icon: 'folder_open', action: 'reveal' },\n ];\n\n const menuFilter = this.#callbacks.menuFilter;\n const items = menuFilter\n ? allItems.filter(item => item.separator || menuFilter(item.action, filePath) !== false)\n : allItems;\n\n // Collapse consecutive / leading / trailing separators\n const cleaned = [];\n for (const item of items) {\n if (item.separator) {\n if (cleaned.length > 0 && !cleaned[cleaned.length - 1].separator) cleaned.push(item);\n } else {\n cleaned.push(item);\n }\n }\n while (cleaned.length > 0 && cleaned[cleaned.length - 1].separator) cleaned.pop();\n\n const menu = document.createElement('div');\n menu.className = 'nb-context-menu';\n menu.setAttribute('role', 'menu');\n\n for (const item of cleaned) {\n if (item.separator) {\n const sep = document.createElement('div');\n sep.className = 'nb-context-menu__separator';\n menu.appendChild(sep);\n continue;\n }\n\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.setAttribute('role', 'menuitem');\n btn.className = 'nb-context-menu__item';\n if (item.danger) btn.classList.add('nb-context-menu__item--danger');\n if (item.disabled) btn.classList.add('nb-context-menu__item--disabled');\n\n btn.innerHTML = `<span class=\"material-symbols-outlined\">${item.icon}</span><span>${this.#escHtml(item.label)}</span>${\n item.shortcut ? `<span class=\"nb-context-menu__shortcut\">${item.shortcut}</span>` : ''\n }`;\n\n if (!item.disabled) {\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.#hideContextMenu();\n this.#handleContextMenuAction(item.action, filePath);\n });\n }\n menu.appendChild(btn);\n }\n\n this.#contextMenuReturnFocus = document.activeElement;\n document.body.appendChild(menu);\n this.#contextMenuEl = menu;\n\n // BUG-0040. Inside the window, at most its height, scrolling when\n // longer; the keyboard starts on the first item it can use.\n placeMenuInViewport(menu, x, y);\n focusMenuItem(this.#contextMenuItems()[0]);\n\n // Animate in\n requestAnimationFrame(() => menu.classList.add('visible'));\n\n // Dismiss on outside click or Escape\n setTimeout(() => {\n document.addEventListener('click', this.#boundHideContextMenu, true);\n document.addEventListener('contextmenu', this.#boundHideContextMenu, true);\n }, 0);\n document.addEventListener('keydown', this.#onKeyDown);\n }\n\n #hideContextMenu() {\n if (!this.#contextMenuEl) return;\n const returnTo = this.#contextMenuReturnFocus;\n const held = this.#contextMenuEl.contains(document.activeElement);\n this.#contextMenuEl.remove();\n this.#contextMenuEl = null;\n this.#contextMenuReturnFocus = null;\n document.removeEventListener('click', this.#boundHideContextMenu, true);\n document.removeEventListener('contextmenu', this.#boundHideContextMenu, true);\n document.removeEventListener('keydown', this.#onKeyDown);\n if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });\n }\n\n /** BUG-0040. The menu's items the keyboard can land on. */\n #contextMenuItems() {\n return this.#contextMenuEl\n ? [...this.#contextMenuEl.querySelectorAll('.nb-context-menu__item:not(.nb-context-menu__item--disabled)')]\n : [];\n }\n\n #onKeyDown = (e) => {\n if (e.key === 'Escape') { this.#hideContextMenu(); return; }\n // BUG-0040. Up/Down/Home/End move between the items and scroll the\n // focused one into view; Enter and Space are the buttons' own.\n moveMenuFocus(e, this.#contextMenuItems());\n };\n\n #handleContextMenuAction(action, filePath) {\n switch (action) {\n case 'rename': this.#beginInlineRename(filePath); break;\n case 'duplicate': this.#callbacks.onDuplicate?.(filePath); break;\n case 'close': this.#callbacks.onClose?.(filePath); break;\n case 'close-others': this.#callbacks.onCloseOthers?.(filePath); break;\n case 'close-right': this.#callbacks.onCloseToRight?.(filePath); break;\n case 'close-all': this.#callbacks.onCloseAll?.(); break;\n case 'close-saved': this.#callbacks.onCloseSaved?.(); break;\n case 'split-right': this.#callbacks.onSplitRight?.(filePath); break;\n case 'move-to-other':this.#callbacks.onMoveToOtherPane?.(filePath); break;\n case 'reveal': this.#callbacks.onRevealInExplorer?.(filePath); break;\n }\n }\n\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n // Inline rename (contenteditable on tab title)\n // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n #beginInlineRename(filePath) {\n const tabEl = this.#container?.querySelector(`.tab[data-path=\"${CSS.escape(filePath)}\"]`);\n if (!tabEl) return;\n\n const title = tabEl.querySelector('.twm-tab-title');\n if (!title || title.getAttribute('contenteditable') === 'true') return;\n\n const tab = this.#tabs.find(t => t.filePath === filePath);\n if (!tab) return;\n\n // model.notebook, builtin, and readonly files cannot be renamed\n if (tab.fileType === 'model') return;\n if (filePath.startsWith('builtin://')) return;\n if (tab.readonly) return;\n\n const original = title.textContent || '';\n title.setAttribute('contenteditable', 'true');\n title.focus();\n\n try {\n const range = document.createRange();\n range.selectNodeContents(title);\n const sel = window.getSelection();\n sel.removeAllRanges();\n sel.addRange(range);\n } catch (_) { /* ignore */ }\n\n let committed = false;\n\n const finish = (commit) => {\n if (committed) return;\n committed = true;\n\n const newName = (title.textContent || '').trim();\n title.removeAttribute('contenteditable');\n title.removeEventListener('keydown', onKeyDown);\n title.removeEventListener('blur', onBlur, true);\n\n try { window.getSelection()?.removeAllRanges?.(); } catch (_) { /* ignore */ }\n\n if (commit && newName && newName !== original && /^[\\w][\\w.-]*$/.test(newName)) {\n title.textContent = newName;\n const dir = filePath.split('/').slice(0, -1).join('/');\n const ext = filePath.split('.').pop();\n const newPath = `${dir}/${newName}.${ext}`;\n this.#callbacks.onRename?.(filePath, newPath);\n } else {\n title.textContent = original;\n }\n };\n\n const onKeyDown = (e) => {\n if (e.key === 'Enter') { e.preventDefault(); finish(true); }\n else if (e.key === 'Escape') { e.preventDefault(); finish(false); }\n e.stopPropagation();\n };\n\n const onBlur = () => finish(true);\n\n title.addEventListener('keydown', onKeyDown);\n title.addEventListener('blur', onBlur, true);\n }\n\n #escHtml(s) {\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-CVMCZZX2.js";
13
13
  import {
14
14
  DragReorder
15
- } from "./chunk-WVFGV5FT.js";
15
+ } from "./chunk-WUFLGETN.js";
16
16
 
17
17
  // src/ui/components/sortable_list.js
18
18
  var ROW_CLASS = "twm-sortable-list__row";
@@ -1308,4 +1308,4 @@ export {
1308
1308
  openConfirm,
1309
1309
  openModal
1310
1310
  };
1311
- //# sourceMappingURL=chunk-D3COLZZ2.js.map
1311
+ //# sourceMappingURL=chunk-BE2PU3LL.js.map
File without changes
File without changes
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  modalHost
3
- } from "./chunk-D3COLZZ2.js";
3
+ } from "./chunk-BE2PU3LL.js";
4
+ import {
5
+ focusMenuItem,
6
+ moveMenuFocus,
7
+ placeMenuInViewport
8
+ } from "./chunk-WUFLGETN.js";
4
9
 
5
10
  // src/ui/components/context_menu.js
6
11
  var _activeMenu = null;
@@ -47,17 +52,13 @@ function showContextMenu(x, y, items, onAction) {
47
52
  (modalHost() || document.body).appendChild(menu);
48
53
  menu.style.display = "block";
49
54
  _activeMenu = menu;
50
- const rect = menu.getBoundingClientRect();
51
- const left = Math.min(x, window.innerWidth - rect.width - 8);
52
- const top = Math.min(y, window.innerHeight - rect.height - 8);
53
- menu.style.left = `${Math.max(0, left)}px`;
54
- menu.style.top = `${Math.max(0, top)}px`;
55
+ placeMenuInViewport(menu, x, y);
55
56
  setTimeout(() => {
56
57
  document.addEventListener("mousedown", _outsideHandler, { once: true, capture: true });
57
58
  }, 0);
58
59
  document.addEventListener("keydown", _keyHandler);
59
- window.addEventListener("scroll", hideContextMenu, { once: true, capture: true });
60
- _enabledItems(menu)[0]?.focus({ preventScroll: true });
60
+ window.addEventListener("scroll", _scrollHandler, { capture: true });
61
+ focusMenuItem(_enabledItems(menu)[0]);
61
62
  }
62
63
  function hideContextMenu() {
63
64
  if (!_activeMenu) return;
@@ -67,6 +68,7 @@ function hideContextMenu() {
67
68
  _activeMenu = null;
68
69
  _returnFocusTo = null;
69
70
  document.removeEventListener("keydown", _keyHandler);
71
+ window.removeEventListener("scroll", _scrollHandler, { capture: true });
70
72
  if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });
71
73
  }
72
74
  function _enabledItems(menu) {
@@ -79,17 +81,11 @@ function _keyHandler(e) {
79
81
  hideContextMenu();
80
82
  return;
81
83
  }
82
- const items = _enabledItems(_activeMenu);
83
- if (items.length === 0) return;
84
- const at = items.indexOf(document.activeElement);
85
- let next = null;
86
- if (e.key === "ArrowDown") next = items[(at + 1 + items.length) % items.length];
87
- else if (e.key === "ArrowUp") next = items[(at - 1 + items.length) % items.length];
88
- else if (e.key === "Home") next = items[0];
89
- else if (e.key === "End") next = items[items.length - 1];
90
- if (!next) return;
91
- e.preventDefault();
92
- next.focus({ preventScroll: true });
84
+ moveMenuFocus(e, _enabledItems(_activeMenu));
85
+ }
86
+ function _scrollHandler(e) {
87
+ if (_activeMenu && e.target?.nodeType && _activeMenu.contains(e.target)) return;
88
+ hideContextMenu();
93
89
  }
94
90
  function _outsideHandler(e) {
95
91
  if (_activeMenu && !_activeMenu.contains(e.target)) hideContextMenu();
@@ -108,4 +104,4 @@ export {
108
104
  showContextMenu,
109
105
  hideContextMenu
110
106
  };
111
- //# sourceMappingURL=chunk-UWO43BZU.js.map
107
+ //# sourceMappingURL=chunk-F35XSQWA.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ui/components/context_menu.js"],
4
+ "sourcesContent": ["/**\n * context_menu.js \u2014 small popup menu in the `.context-menu` idiom the\n * DataTable's own right-click menu uses (./data_table.js).\n *\n * Usage:\n * showContextMenu(x, y, [\n * { label: 'Open', icon: 'open_in_new', action: 'open' },\n * { separator: true },\n * { label: 'Delete', icon: 'delete', action: 'delete', danger: true },\n * ], (action) => { ... });\n *\n * The menu auto-closes on outside click / scroll / Escape.\n *\n * \u2500\u2500 It is operable by keyboard \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 * Items were bare `<div>`s with a click listener: no role, no tab stop, no\n * arrow-key movement, and `showContextMenu` never moved focus into the menu.\n * A consumer that opened this from Shift+F10 \u2014 the rail in Tables does \u2014 put a\n * menu on screen that the keyboard could only dismiss. Every command in it was\n * unreachable without a pointer, and nothing said so.\n *\n * So an item is a `<button role=\"menuitem\">`, the first enabled one takes focus\n * when the menu opens, Up/Down/Home/End move between them and wrap, Enter and\n * Space activate, Escape closes, and focus returns to whatever had it before.\n * Disabled items are skipped by the arrows rather than focusable-but-inert.\n * `<button>` rather than a div with `tabindex`: the browser then gives Enter and\n * Space for free and screen readers announce it without further help.\n *\n * \u2500\u2500 It stays inside the window (BUG-0040) \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 * The top was clamped at 0 but nothing capped the height, so a long menu ran\n * off the bottom and could not scroll. `placeMenuInViewport` caps it at the\n * window's height less a margin; it scrolls inside, the arrows scroll the\n * focused item into view, and scrolling the menu itself does not close it.\n */\n\nimport { modalHost } from './modal.js';\nimport { placeMenuInViewport, moveMenuFocus, focusMenuItem } from '../utils/menu_viewport.js';\n\nlet _activeMenu = null;\nlet _returnFocusTo = null;\n\nexport function showContextMenu(x, y, items, onAction) {\n hideContextMenu();\n\n // Whatever had focus when the menu opened gets it back when the menu\n // closes. Without this a keyboard user who presses Escape is returned to\n // `document.body` and has to tab back to where they were.\n _returnFocusTo = document.activeElement;\n\n const menu = document.createElement('div');\n menu.className = 'twm-context-menu ea-context-menu';\n menu.setAttribute('role', 'menu');\n\n for (const it of items) {\n if (it.separator) {\n const sep = document.createElement('div');\n sep.className = 'twm-context-menu__separator';\n sep.setAttribute('role', 'separator');\n menu.appendChild(sep);\n continue;\n }\n const row = document.createElement('button');\n row.type = 'button';\n row.setAttribute('role', 'menuitem');\n let cls = 'twm-context-menu-item';\n if (it.danger) cls += ' twm-delete-node';\n if (it.disabled) cls += ' disabled';\n row.className = cls;\n if (it.disabled) {\n row.disabled = true;\n row.setAttribute('aria-disabled', 'true');\n }\n // A DISABLED ROW WITH NO EXPLANATION IS A DEAD CONTROL. `title` was\n // accepted by callers and rendered by nothing \u2014 the item was built with\n // one, the row silently dropped it, and the user got a greyed line with\n // no way to learn why. It is the same shape as the classes this\n // repository keeps finding: no error, no throw, and invisible to every\n // test that checks the row is disabled.\n if (it.title) row.title = it.title;\n row.innerHTML = `\n <span class=\"material-symbols-outlined\">${it.icon || ''}</span>\n <span>${escapeHtml(it.label)}</span>\n `;\n if (!it.disabled) {\n row.addEventListener('click', (e) => {\n e.stopPropagation();\n hideContextMenu();\n onAction?.(it.action);\n });\n }\n menu.appendChild(row);\n }\n\n // C25. THE SAME QUESTION A MODAL ASKS: which window is the user in? A\n // consumer spanning two browser windows sets the host when its focus moves;\n // null \u2014 every consumer today \u2014 is `document.body`, exactly as before. A\n // menu in the wrong window is worse than a modal in the wrong window,\n // because it is positioned at coordinates from the OTHER one.\n (modalHost() || document.body).appendChild(menu);\n menu.style.display = 'block';\n _activeMenu = menu;\n\n // Position inside the window, scrolling when taller than it (BUG-0040).\n placeMenuInViewport(menu, x, y);\n\n // Close on outside click / scroll / Escape. A scroll INSIDE the menu is\n // the menu being read, not the page moving under it.\n setTimeout(() => {\n document.addEventListener('mousedown', _outsideHandler, { once: true, capture: true });\n }, 0);\n document.addEventListener('keydown', _keyHandler);\n window.addEventListener('scroll', _scrollHandler, { capture: true });\n\n // Focus the first item the keyboard can actually use. `preventScroll` so a\n // menu opened near the bottom of a long page does not jump it.\n focusMenuItem(_enabledItems(menu)[0]);\n}\n\nexport function hideContextMenu() {\n if (!_activeMenu) return;\n const returnTo = _returnFocusTo;\n const held = _activeMenu.contains(document.activeElement);\n _activeMenu.remove();\n _activeMenu = null;\n _returnFocusTo = null;\n document.removeEventListener('keydown', _keyHandler);\n window.removeEventListener('scroll', _scrollHandler, { capture: true });\n // Only take focus back if the menu still had it. A click elsewhere has\n // already moved focus deliberately and must not be undone.\n if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });\n}\n\nfunction _enabledItems(menu) {\n return [...menu.querySelectorAll('.twm-context-menu-item:not(.disabled)')];\n}\n\n/** Up/Down/Home/End move; the list WRAPS, which is what a menu of four items\n * wants and what every desktop menu does. Enter and Space are the button's\n * own, so they are not bound here. */\nfunction _keyHandler(e) {\n if (!_activeMenu) return;\n if (e.key === 'Escape') { e.preventDefault(); hideContextMenu(); return; }\n moveMenuFocus(e, _enabledItems(_activeMenu));\n}\n\nfunction _scrollHandler(e) {\n if (_activeMenu && e.target?.nodeType && _activeMenu.contains(e.target)) return;\n hideContextMenu();\n}\n\nfunction _outsideHandler(e) {\n if (_activeMenu && !_activeMenu.contains(e.target)) hideContextMenu();\n}\n\nfunction escapeHtml(s) {\n return String(s).replace(/[&<>\"']/g, (c) => ({\n '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;',\n }[c]));\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -264,8 +264,12 @@ var ActionDropdown = class _ActionDropdown {
264
264
  *
265
265
  * @param {HTMLElement} trigger - The element the dropdown opens from
266
266
  * @param {HTMLElement} menu - The dropdown menu element to position
267
+ * @param {Object} [opts]
268
+ * @param {'start'|'end'} [opts.align='start'] - Which trigger edge the menu
269
+ * lines up with first: `'start'` its left edge, `'end'` its right edge.
270
+ * Either flips to the other edge when it would leave the window.
267
271
  */
268
- static position(trigger, menu) {
272
+ static position(trigger, menu, { align = "start" } = {}) {
269
273
  if (!trigger || !menu) return;
270
274
  Object.assign(menu.style, {
271
275
  position: "fixed",
@@ -297,9 +301,17 @@ var ActionDropdown = class _ActionDropdown {
297
301
  maxHeight = spaceAbove;
298
302
  top = EDGE_MARGIN;
299
303
  }
300
- let left = triggerRect.left;
301
- if (left + menuRect.width > vw - EDGE_MARGIN) {
304
+ let left;
305
+ if (align === "end") {
302
306
  left = triggerRect.right - menuRect.width;
307
+ if (left < EDGE_MARGIN) {
308
+ left = triggerRect.left;
309
+ }
310
+ } else {
311
+ left = triggerRect.left;
312
+ if (left + menuRect.width > vw - EDGE_MARGIN) {
313
+ left = triggerRect.right - menuRect.width;
314
+ }
303
315
  }
304
316
  left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));
305
317
  Object.assign(menu.style, {
@@ -511,4 +523,4 @@ export {
511
523
  ActionDropdown,
512
524
  SlideOutPanel
513
525
  };
514
- //# sourceMappingURL=chunk-RKNFBX5K.js.map
526
+ //# sourceMappingURL=chunk-MIW7D6WC.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/ui/base/component_base.js", "../src/ui/components/action_dropdown.js", "../src/ui/components/slide_out_panel.js"],
4
- "sourcesContent": ["/**\r\n * ComponentBase (js_new)\r\n *\r\n * Ultra-lightweight base for leaf UI widgets rendered inside controllers/panels.\r\n * Handles props/state updates and deterministic teardown without storing data on\r\n * DOM elements.\r\n */\r\nexport class ComponentBase {\r\n constructor({ eventBus, logger } = {}) {\r\n if (new.target === ComponentBase) {\r\n throw new Error('ComponentBase is abstract \u2013 extend it instead');\r\n }\r\n\r\n this.eventBus = eventBus;\r\n this.logger = logger;\r\n this._mounted = false;\r\n }\r\n\r\n mount(/* container, props */) {\r\n throw new Error('ComponentBase.mount must be implemented by subclasses');\r\n }\r\n\r\n update(/* props */) {\r\n // default no-op for stateless components\r\n }\r\n\r\n dispose() {\r\n this._mounted = false;\r\n }\r\n}\r\n", "/**\n * action_dropdown.js\n *\n * Reusable dropdown component for action buttons (Add Scenario, Add Widget, etc).\n * Handles opening, closing, auto-positioning, keyboard navigation, and outside clicks.\n *\n * Auto-positioning: measures available viewport space around the trigger element\n * and picks the best direction (above/below) and alignment (left/right).\n * Adds scrolling when the menu would exceed available vertical space.\n */\n\n/** Viewport margin in px \u2014 dropdown stays this far from edges. */\nconst EDGE_MARGIN = 8;\n/** Gap between trigger and dropdown in px. */\nconst GAP = 8;\n\n/**\n * ActionDropdown - A reusable dropdown component with smart auto-positioning.\n *\n * Usage:\n * ```javascript\n * const dropdown = new ActionDropdown({\n * trigger: buttonElement,\n * options: [\n * { type: 'option1', label: 'Option 1', icon: 'icon_name', description: 'Description' },\n * { separator: true },\n * { type: 'option2', label: 'Option 2', icon: 'icon_name', description: 'Description' }\n * ],\n * onSelect: (option) => console.log('Selected:', option.type),\n * });\n *\n * // Control programmatically\n * dropdown.open();\n * dropdown.close();\n * dropdown.toggle();\n * dropdown.destroy();\n * ```\n *\n * \u2500\u2500 Separators \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\u2500\u2500\u2500\u2500\u2500\n * An entry `{ separator: true }` draws a divider instead of a button: a\n * `<div role=\"separator\" class=\"twm-action-dropdown-separator\">`, which takes\n * no focus, is never reached by the arrow keys (they move between the option\n * buttons only) and never reaches `onSelect` (it has no click handler). A\n * separator only separates: one at the start or the end of the list, or one\n * right after another, is dropped, so a list assembled from groups that may\n * come out empty never draws a stray or doubled rule.\n */\nexport class ActionDropdown {\n /**\n * @param {Object} config\n * @param {HTMLElement} config.trigger - Button element that triggers the dropdown\n * @param {Array} config.options - Array of option objects with { type, label, icon, description? },\n * or `{ separator: true }` for a divider between them\n * @param {Function} config.onSelect - Callback when an option is selected\n * @param {string} [config.className=''] - Additional CSS class for the dropdown\n * @param {string} [config.menuId] - Optional ID for the menu element\n */\n constructor(config) {\n this.trigger = config.trigger;\n this.options = config.options || [];\n this.onSelect = config.onSelect;\n this.className = config.className || '';\n this.menuId = config.menuId;\n\n this.isOpen = false;\n this.menuEl = null;\n\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);\n\n this._init();\n }\n\n /**\n * Initialize the dropdown.\n * @private\n */\n _init() {\n if (!this.trigger) {\n console.warn('[ActionDropdown] No trigger element provided');\n return;\n }\n\n this._createMenu();\n this.trigger.addEventListener('click', this._boundHandleTriggerClick);\n }\n\n /**\n * Create the dropdown menu element.\n * @private\n */\n _createMenu() {\n const menu = document.createElement('div');\n menu.className = `twm-action-dropdown-menu ${this.className}`.trim();\n menu.setAttribute('role', 'menu');\n menu.hidden = true;\n\n if (this.menuId) {\n menu.id = this.menuId;\n }\n\n this._renderOptions(menu);\n\n // Append to body so position:fixed works without clipping.\n document.body.appendChild(menu);\n\n this.menuEl = menu;\n }\n\n /**\n * Render option buttons, and dividers for separator entries, into a\n * container element. `data-index` stays the entry's index in `options`.\n * @param {HTMLElement} container\n * @private\n */\n _renderOptions(container) {\n container.innerHTML = '';\n let pendingSeparator = false;\n this.options.forEach((opt, index) => {\n if (opt?.separator) {\n // Drawn only once an option follows it, and only if an option\n // came before it: leading, trailing and doubled separators\n // collapse.\n pendingSeparator = container.childElementCount > 0;\n return;\n }\n if (pendingSeparator) {\n const sep = document.createElement('div');\n sep.className = 'twm-action-dropdown-separator';\n sep.setAttribute('role', 'separator');\n container.appendChild(sep);\n pendingSeparator = false;\n }\n\n const optionBtn = document.createElement('button');\n optionBtn.type = 'button';\n optionBtn.className = 'twm-action-dropdown-option';\n optionBtn.dataset.index = index;\n optionBtn.dataset.type = opt.type;\n optionBtn.setAttribute('role', 'menuitem');\n\n const iconHtml = opt.icon\n ? `<span class=\"material-symbols-outlined twm-option-icon\">${opt.icon}</span>`\n : '';\n\n const descHtml = opt.description\n ? `<span class=\"twm-option-desc\">${opt.description}</span>`\n : '';\n\n optionBtn.innerHTML = `\n ${iconHtml}\n <span class=\"option-text\">\n <span class=\"twm-option-label\">${opt.label}</span>\n ${descHtml}\n </span>\n `;\n\n optionBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this._selectOption(opt);\n });\n\n container.appendChild(optionBtn);\n });\n }\n\n /**\n * Handle trigger button click.\n * @param {MouseEvent} e\n * @private\n */\n _handleTriggerClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.toggle();\n }\n\n /**\n * Toggle the dropdown open/closed.\n * @param {boolean} [forceState] - Optional forced state\n */\n toggle(forceState) {\n const shouldOpen = typeof forceState === 'boolean' ? forceState : !this.isOpen;\n if (shouldOpen) {\n this.open();\n } else {\n this.close();\n }\n }\n\n /**\n * Open the dropdown with auto-positioning.\n */\n open() {\n if (this.isOpen || !this.menuEl) return;\n\n this.isOpen = true;\n this.menuEl.hidden = false;\n\n // Position against trigger\n ActionDropdown.position(this.trigger, this.menuEl);\n\n // Animate in.\n //\n // OPTIONAL CHAINING, AND IT IS LOAD-BEARING. This callback runs a frame\n // after `open()` returned, and `destroy()` sets `this.menuEl = null`\n // (see below) \u2014 so a dropdown that is opened and then destroyed inside\n // one frame threw an uncaught `TypeError: Cannot read properties of\n // null` out of an animation-frame callback, where no caller has a stack\n // to catch it. That is not a hypothetical: it is what a user does every\n // time they open a picker and then click something that unmounts the\n // pane around it, and it was reproduced eighteen times in one run of a\n // consumer's settings suite. Nothing is lost by skipping the class \u2014 the\n // element it would have been added to no longer exists.\n requestAnimationFrame(() => {\n this.menuEl?.classList.add('visible');\n });\n\n // Update trigger state\n this.trigger?.classList.add('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'true');\n }\n\n // Add document listeners\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n document.addEventListener('keydown', this._boundHandleKeydown);\n\n // Focus first option for keyboard accessibility\n const firstOption = this.menuEl.querySelector('.twm-action-dropdown-option');\n firstOption?.focus();\n }\n\n /**\n * Close the dropdown.\n */\n close() {\n if (!this.isOpen) return;\n\n this.isOpen = false;\n\n if (this.menuEl) {\n this.menuEl.classList.remove('visible');\n this.menuEl.hidden = true;\n }\n\n // Update trigger state. `aria-expanded` belongs on the TRIGGER and has\n // to be written on every close, not only on the ones a click caused \u2014\n // an embedder that synced it from its own click handler was announcing\n // an expanded menu to a screen reader every time Escape or an outside\n // click dismissed one. The component knows when it closed; nothing else\n // reliably does.\n this.trigger?.classList.remove('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'false');\n }\n\n // Remove document listeners\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n /**\n * Handle document click (close on outside click).\n * @param {MouseEvent} e\n * @private\n */\n _handleDocumentClick(e) {\n if (!this.isOpen) return;\n\n // Don't close if clicking inside dropdown or trigger\n if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {\n return;\n }\n\n this.close();\n }\n\n /**\n * Handle keyboard events.\n * @param {KeyboardEvent} e\n * @private\n */\n _handleKeydown(e) {\n if (e.key === 'Escape') {\n // AND NOBODY ELSE GETS IT. An open dropdown is the innermost thing\n // on screen, so Escape means \"close this\" and nothing further \u2014\n // but the event was left to bubble, and inside a `ManagedWindow`\n // (which binds its own Escape to dismiss) that meant one keystroke\n // closed the dropdown AND the dialog around it. The user loses a\n // form they were filling in because they changed their mind about\n // one field.\n e.preventDefault();\n e.stopPropagation();\n this.close();\n this.trigger?.focus();\n return;\n }\n\n // Arrow key navigation\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n this._navigateOptions(e.key === 'ArrowDown' ? 1 : -1);\n }\n }\n\n /**\n * Navigate options with arrow keys.\n * @param {number} direction - 1 for down, -1 for up\n * @private\n */\n _navigateOptions(direction) {\n const options = this.menuEl?.querySelectorAll('.twm-action-dropdown-option');\n if (!options || options.length === 0) return;\n\n const currentIndex = Array.from(options).findIndex(opt => opt === document.activeElement);\n let nextIndex = currentIndex + direction;\n\n if (nextIndex < 0) nextIndex = options.length - 1;\n if (nextIndex >= options.length) nextIndex = 0;\n\n options[nextIndex]?.focus();\n }\n\n /**\n * Handle option selection.\n * @param {Object} option - The selected option\n * @private\n */\n _selectOption(option) {\n this.close();\n\n if (this.onSelect) {\n this.onSelect(option);\n }\n }\n\n /**\n * Update the options dynamically.\n * @param {Array} newOptions - New options array\n */\n setOptions(newOptions) {\n this.options = newOptions;\n if (this.menuEl) {\n this._renderOptions(this.menuEl);\n }\n }\n\n /**\n * Destroy the dropdown and clean up.\n */\n destroy() {\n this.close();\n\n // Remove trigger listener\n this.trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n\n // Remove menu element\n this.menuEl?.remove();\n this.menuEl = null;\n\n this.trigger = null;\n this.options = [];\n this.onSelect = null;\n }\n\n // =========================================================================\n // STATIC \u2014 shared auto-positioning for any trigger + menu pair\n // =========================================================================\n\n /**\n * Position a dropdown menu relative to a trigger element.\n * Uses `position: fixed` to avoid overflow clipping.\n * Picks above/below based on available space, aligns left/right edge,\n * and applies `max-height` + `overflow-y: auto` when the menu is taller\n * than the available space.\n *\n * @param {HTMLElement} trigger - The element the dropdown opens from\n * @param {HTMLElement} menu - The dropdown menu element to position\n */\n static position(trigger, menu) {\n if (!trigger || !menu) return;\n\n // Reset any prior inline position so natural size can be measured.\n Object.assign(menu.style, {\n position: 'fixed',\n top: 'auto',\n bottom: 'auto',\n left: 'auto',\n right: 'auto',\n maxHeight: '',\n overflowY: ''\n });\n\n const triggerRect = trigger.getBoundingClientRect();\n const menuRect = menu.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // \u2500\u2500 Vertical: prefer below, flip above if insufficient room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;\n const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;\n\n let top;\n let maxHeight;\n\n if (menuRect.height <= spaceBelow) {\n // Fits below \u2014 use natural height.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else if (menuRect.height <= spaceAbove) {\n // Fits above \u2014 use natural height.\n top = triggerRect.top - GAP - menuRect.height;\n maxHeight = spaceAbove;\n } else if (spaceBelow >= spaceAbove) {\n // More space below \u2014 scroll.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else {\n // More space above \u2014 scroll.\n maxHeight = spaceAbove;\n top = EDGE_MARGIN;\n }\n\n // \u2500\u2500 Horizontal: align left-edge with trigger, flip if off-screen \u2500\u2500\n let left = triggerRect.left;\n\n if (left + menuRect.width > vw - EDGE_MARGIN) {\n // Align right edges instead.\n left = triggerRect.right - menuRect.width;\n }\n\n // Still off-screen? Clamp to edges.\n left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));\n\n Object.assign(menu.style, {\n position: 'fixed',\n top: `${Math.round(top)}px`,\n left: `${Math.round(left)}px`,\n maxHeight: `${Math.round(maxHeight)}px`,\n overflowY: 'auto'\n });\n }\n}\n\nexport default ActionDropdown;\n", "/**\n * SlideOutPanel\n *\n * Reusable inline slide-out config panel that overlays its host container\n * from the right edge. Used by pages to show element-specific config\n * (plot config, widget config, ETL node config) without the global right panel.\n *\n * Host container must have `position: relative; overflow: hidden`.\n */\nimport { ComponentBase } from '../base/component_base.js';\n\nexport class SlideOutPanel extends ComponentBase {\n\n /** @type {HTMLElement} */ #container = null;\n /** @type {HTMLElement} */ #backdrop = null;\n /** @type {HTMLElement} */ #panel = null;\n /** @type {HTMLElement} */ #headerIcon = null;\n /** @type {HTMLElement} */ #headerTitle = null;\n /** @type {HTMLElement} */ #body = null;\n /** @type {number} */ #width;\n /** @type {Function|null} */#onClose;\n /** @type {boolean} */ #open = false;\n /** @type {ResizeObserver|null} */ #hostObserver = null;\n\n /**\n * @param {object} opts\n * @param {number} [opts.width=380] Panel width in px.\n * @param {Function} [opts.onClose] Called after the panel closes.\n */\n constructor({ width = 700, onClose } = {}) {\n super();\n this.#width = width;\n this.#onClose = onClose ?? null;\n }\n\n // \u2500\u2500\u2500 Lifecycle \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Mount the panel DOM into a host container.\n * @param {HTMLElement} container Must have position:relative; overflow:hidden.\n */\n mount(container) {\n if (this._mounted) return;\n this.#container = container;\n\n // Backdrop (transparent click-catcher)\n this.#backdrop = document.createElement('div');\n this.#backdrop.className = 'twm-slide-out-panel__backdrop';\n this.#backdrop.addEventListener('click', this.#handleBackdropClick);\n\n // Panel\n this.#panel = document.createElement('div');\n this.#panel.className = 'twm-slide-out-panel';\n this.#panel.style.width = `${this.#width}px`;\n\n // Header\n const header = document.createElement('div');\n header.className = 'twm-slide-out-panel__header';\n\n this.#headerIcon = document.createElement('span');\n this.#headerIcon.className = 'twm-slide-out-panel__icon material-symbols-outlined';\n\n this.#headerTitle = document.createElement('span');\n this.#headerTitle.className = 'twm-slide-out-panel__title';\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'twm-slide-out-panel__close twm-btn-icon';\n closeBtn.type = 'button';\n closeBtn.title = 'Close';\n closeBtn.innerHTML = '<span class=\"material-symbols-outlined\">close</span>';\n closeBtn.addEventListener('click', this.#handleCloseClick);\n\n header.append(this.#headerIcon, this.#headerTitle, closeBtn);\n\n // Body (scrollable content area)\n this.#body = document.createElement('div');\n this.#body.className = 'twm-slide-out-panel__body';\n\n this.#panel.append(header, this.#body);\n this.#container.append(this.#backdrop, this.#panel);\n\n this._mounted = true;\n }\n\n dispose() {\n this.close();\n this.#backdrop?.remove();\n this.#panel?.remove();\n this.#backdrop = null;\n this.#panel = null;\n this.#body = null;\n this.#container = null;\n super.dispose();\n }\n\n // \u2500\u2500\u2500 Public API \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\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** The inner content element \u2014 callers render config into this. */\n get contentEl() { return this.#body; }\n\n get isOpen() { return this.#open; }\n\n /**\n * Open (or re-open) the panel with a new title/icon.\n * Clears previous content. Returns contentEl for rendering.\n * @param {string} title\n * @param {string} icon Material Symbols icon name.\n * @returns {HTMLElement} contentEl\n */\n open(title, icon) {\n if (!this._mounted) return null;\n\n this.#body.innerHTML = '';\n this.#headerIcon.textContent = icon || '';\n this.#headerIcon.style.display = icon ? '' : 'none';\n this.#headerTitle.textContent = title || '';\n\n if (!this.#open) {\n this.#open = true;\n this.#backdrop.classList.add('twm-slide-out-panel__backdrop--visible');\n this.#panel.classList.add('twm-slide-out-panel--open');\n document.addEventListener('keydown', this.#handleEsc);\n\n // The host may be a scroll container \u2014 absolutely-positioned\n // descendants of one DO scroll along with the content\n // (Web spec: a position:absolute element is clipped + offset\n // by its containing scroll context). We escape that by\n // switching the panel to `position: fixed` while open and\n // tracking the host's bounding rect on scroll / resize, so\n // the panel stays pinned to the host's visible frame at\n // full height regardless of how the user scrolls inside.\n this.#trackGeometry();\n window.addEventListener('scroll', this.#trackGeometry, true);\n window.addEventListener('resize', this.#trackGeometry);\n this.#observeHost();\n }\n\n return this.#body;\n }\n\n /** Close the panel and clear content. */\n close() {\n if (!this.#open) return;\n this.#open = false;\n\n this.#panel.classList.remove('twm-slide-out-panel--open');\n this.#backdrop.classList.remove('twm-slide-out-panel__backdrop--visible');\n document.removeEventListener('keydown', this.#handleEsc);\n window.removeEventListener('scroll', this.#trackGeometry, true);\n window.removeEventListener('resize', this.#trackGeometry);\n this.#hostObserver?.disconnect();\n this.#hostObserver = null;\n // Leave the inline position:fixed geometry in place. The panel\n // slides off-screen via the transform animation (translateX\n // 100%); the next open() re-runs #trackGeometry which will\n // refresh top/left/height from the host's current rect.\n\n this.#body.innerHTML = '';\n this.#onClose?.();\n }\n\n // \u2500\u2500\u2500 Event handlers (arrow fns for stable `this`) \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 #handleBackdropClick = () => { this.close(); };\n\n #handleCloseClick = () => { this.close(); };\n\n #handleEsc = (e) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n this.close();\n }\n };\n\n /** The host moves or resizes without any window event when a tile\n * splitter is dragged or a dock panel toggles, and the fixed geometry\n * would then stay at the old coordinates. Observe the host AND each\n * ancestor: a host can move without changing size of its own (a dock\n * panel toggling beside a fixed-width tile shrinks the content area,\n * an ancestor, and the tile only shifts). The callback runs\n * synchronously, after layout and before paint, so the panel follows\n * a drag without a frame of lag; it only writes the panel's own\n * `position: fixed` geometry, which is out of flow and resizes\n * nothing observed, so it cannot loop. */\n #observeHost() {\n this.#hostObserver = new ResizeObserver(this.#trackGeometry);\n for (let el = this.#container; el; el = el.parentElement) {\n this.#hostObserver.observe(el);\n }\n }\n\n /** Pin the panel to the right edge of its host's *visible* frame\n * using `position: fixed`. Re-run on every window scroll/resize and\n * every host resize (#observeHost) so the panel always overlays the\n * host correctly even when the host itself sits inside a scrolled\n * ancestor.\n *\n * UNDER A CSS `zoom` (the shell's content zoom scales a tile body),\n * `getBoundingClientRect()` answers in screen pixels, which already\n * carry the zoom, while the panel's own `top`/`left`/`height` are\n * CSS pixels that the same zoom multiplies again. Copied across\n * unchanged, every length was scaled twice: at 150% the panel landed\n * right of and below its host, at 50% it was half the host's height.\n * So the rect is divided by the panel's effective zoom\n * (`currentCSSZoom`: 1 when nothing above it zooms, `undefined` in an\n * engine that predates it), and the width subtracted is the panel's\n * laid-out one, which `min-width` may hold above the requested\n * width. Rounding happens in screen pixels, so at 100% the output is\n * exactly what it always was.\n *\n * NEVER WIDER THAN THE HOST (BUG-0035). The right edge is pinned, so a\n * panel wider than its host (700px by default, `min-width: 400px`)\n * reached past the host's left edge across the neighbouring pane. The\n * host's width, in the panel's CSS pixels, goes on the panel as\n * `--twm-slide-out-host-w`, which the stylesheet uses as `max-width`\n * and as the ceiling of the 400px `min-width`; it is set BEFORE the\n * width is read so the read sees the capped layout, and the width is\n * capped here too for an engine that lays nothing out. */\n #trackGeometry = () => {\n if (!this.#open || !this.#panel || !this.#container) return;\n const r = this.#container.getBoundingClientRect();\n const zoom = this.#panel.currentCSSZoom || 1;\n const hostWidth = r.width / zoom;\n const s = this.#panel.style;\n s.setProperty('--twm-slide-out-host-w', `${hostWidth}px`);\n const width = Math.min(this.#panel.offsetWidth || this.#width, hostWidth);\n s.position = 'fixed';\n s.top = `${Math.round(r.top) / zoom}px`;\n s.left = `${Math.round(r.right - width * zoom) / zoom}px`;\n s.right = 'auto';\n s.height = `${Math.round(r.height) / zoom}px`;\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\r\n * ComponentBase (js_new)\r\n *\r\n * Ultra-lightweight base for leaf UI widgets rendered inside controllers/panels.\r\n * Handles props/state updates and deterministic teardown without storing data on\r\n * DOM elements.\r\n */\r\nexport class ComponentBase {\r\n constructor({ eventBus, logger } = {}) {\r\n if (new.target === ComponentBase) {\r\n throw new Error('ComponentBase is abstract \u2013 extend it instead');\r\n }\r\n\r\n this.eventBus = eventBus;\r\n this.logger = logger;\r\n this._mounted = false;\r\n }\r\n\r\n mount(/* container, props */) {\r\n throw new Error('ComponentBase.mount must be implemented by subclasses');\r\n }\r\n\r\n update(/* props */) {\r\n // default no-op for stateless components\r\n }\r\n\r\n dispose() {\r\n this._mounted = false;\r\n }\r\n}\r\n", "/**\n * action_dropdown.js\n *\n * Reusable dropdown component for action buttons (Add Scenario, Add Widget, etc).\n * Handles opening, closing, auto-positioning, keyboard navigation, and outside clicks.\n *\n * Auto-positioning: measures available viewport space around the trigger element\n * and picks the best direction (above/below) and alignment (left/right).\n * Adds scrolling when the menu would exceed available vertical space.\n */\n\n/** Viewport margin in px \u2014 dropdown stays this far from edges. */\nconst EDGE_MARGIN = 8;\n/** Gap between trigger and dropdown in px. */\nconst GAP = 8;\n\n/**\n * ActionDropdown - A reusable dropdown component with smart auto-positioning.\n *\n * Usage:\n * ```javascript\n * const dropdown = new ActionDropdown({\n * trigger: buttonElement,\n * options: [\n * { type: 'option1', label: 'Option 1', icon: 'icon_name', description: 'Description' },\n * { separator: true },\n * { type: 'option2', label: 'Option 2', icon: 'icon_name', description: 'Description' }\n * ],\n * onSelect: (option) => console.log('Selected:', option.type),\n * });\n *\n * // Control programmatically\n * dropdown.open();\n * dropdown.close();\n * dropdown.toggle();\n * dropdown.destroy();\n * ```\n *\n * \u2500\u2500 Separators \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\u2500\u2500\u2500\u2500\u2500\n * An entry `{ separator: true }` draws a divider instead of a button: a\n * `<div role=\"separator\" class=\"twm-action-dropdown-separator\">`, which takes\n * no focus, is never reached by the arrow keys (they move between the option\n * buttons only) and never reaches `onSelect` (it has no click handler). A\n * separator only separates: one at the start or the end of the list, or one\n * right after another, is dropped, so a list assembled from groups that may\n * come out empty never draws a stray or doubled rule.\n */\nexport class ActionDropdown {\n /**\n * @param {Object} config\n * @param {HTMLElement} config.trigger - Button element that triggers the dropdown\n * @param {Array} config.options - Array of option objects with { type, label, icon, description? },\n * or `{ separator: true }` for a divider between them\n * @param {Function} config.onSelect - Callback when an option is selected\n * @param {string} [config.className=''] - Additional CSS class for the dropdown\n * @param {string} [config.menuId] - Optional ID for the menu element\n */\n constructor(config) {\n this.trigger = config.trigger;\n this.options = config.options || [];\n this.onSelect = config.onSelect;\n this.className = config.className || '';\n this.menuId = config.menuId;\n\n this.isOpen = false;\n this.menuEl = null;\n\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);\n\n this._init();\n }\n\n /**\n * Initialize the dropdown.\n * @private\n */\n _init() {\n if (!this.trigger) {\n console.warn('[ActionDropdown] No trigger element provided');\n return;\n }\n\n this._createMenu();\n this.trigger.addEventListener('click', this._boundHandleTriggerClick);\n }\n\n /**\n * Create the dropdown menu element.\n * @private\n */\n _createMenu() {\n const menu = document.createElement('div');\n menu.className = `twm-action-dropdown-menu ${this.className}`.trim();\n menu.setAttribute('role', 'menu');\n menu.hidden = true;\n\n if (this.menuId) {\n menu.id = this.menuId;\n }\n\n this._renderOptions(menu);\n\n // Append to body so position:fixed works without clipping.\n document.body.appendChild(menu);\n\n this.menuEl = menu;\n }\n\n /**\n * Render option buttons, and dividers for separator entries, into a\n * container element. `data-index` stays the entry's index in `options`.\n * @param {HTMLElement} container\n * @private\n */\n _renderOptions(container) {\n container.innerHTML = '';\n let pendingSeparator = false;\n this.options.forEach((opt, index) => {\n if (opt?.separator) {\n // Drawn only once an option follows it, and only if an option\n // came before it: leading, trailing and doubled separators\n // collapse.\n pendingSeparator = container.childElementCount > 0;\n return;\n }\n if (pendingSeparator) {\n const sep = document.createElement('div');\n sep.className = 'twm-action-dropdown-separator';\n sep.setAttribute('role', 'separator');\n container.appendChild(sep);\n pendingSeparator = false;\n }\n\n const optionBtn = document.createElement('button');\n optionBtn.type = 'button';\n optionBtn.className = 'twm-action-dropdown-option';\n optionBtn.dataset.index = index;\n optionBtn.dataset.type = opt.type;\n optionBtn.setAttribute('role', 'menuitem');\n\n const iconHtml = opt.icon\n ? `<span class=\"material-symbols-outlined twm-option-icon\">${opt.icon}</span>`\n : '';\n\n const descHtml = opt.description\n ? `<span class=\"twm-option-desc\">${opt.description}</span>`\n : '';\n\n optionBtn.innerHTML = `\n ${iconHtml}\n <span class=\"option-text\">\n <span class=\"twm-option-label\">${opt.label}</span>\n ${descHtml}\n </span>\n `;\n\n optionBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this._selectOption(opt);\n });\n\n container.appendChild(optionBtn);\n });\n }\n\n /**\n * Handle trigger button click.\n * @param {MouseEvent} e\n * @private\n */\n _handleTriggerClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.toggle();\n }\n\n /**\n * Toggle the dropdown open/closed.\n * @param {boolean} [forceState] - Optional forced state\n */\n toggle(forceState) {\n const shouldOpen = typeof forceState === 'boolean' ? forceState : !this.isOpen;\n if (shouldOpen) {\n this.open();\n } else {\n this.close();\n }\n }\n\n /**\n * Open the dropdown with auto-positioning.\n */\n open() {\n if (this.isOpen || !this.menuEl) return;\n\n this.isOpen = true;\n this.menuEl.hidden = false;\n\n // Position against trigger\n ActionDropdown.position(this.trigger, this.menuEl);\n\n // Animate in.\n //\n // OPTIONAL CHAINING, AND IT IS LOAD-BEARING. This callback runs a frame\n // after `open()` returned, and `destroy()` sets `this.menuEl = null`\n // (see below) \u2014 so a dropdown that is opened and then destroyed inside\n // one frame threw an uncaught `TypeError: Cannot read properties of\n // null` out of an animation-frame callback, where no caller has a stack\n // to catch it. That is not a hypothetical: it is what a user does every\n // time they open a picker and then click something that unmounts the\n // pane around it, and it was reproduced eighteen times in one run of a\n // consumer's settings suite. Nothing is lost by skipping the class \u2014 the\n // element it would have been added to no longer exists.\n requestAnimationFrame(() => {\n this.menuEl?.classList.add('visible');\n });\n\n // Update trigger state\n this.trigger?.classList.add('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'true');\n }\n\n // Add document listeners\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n document.addEventListener('keydown', this._boundHandleKeydown);\n\n // Focus first option for keyboard accessibility\n const firstOption = this.menuEl.querySelector('.twm-action-dropdown-option');\n firstOption?.focus();\n }\n\n /**\n * Close the dropdown.\n */\n close() {\n if (!this.isOpen) return;\n\n this.isOpen = false;\n\n if (this.menuEl) {\n this.menuEl.classList.remove('visible');\n this.menuEl.hidden = true;\n }\n\n // Update trigger state. `aria-expanded` belongs on the TRIGGER and has\n // to be written on every close, not only on the ones a click caused \u2014\n // an embedder that synced it from its own click handler was announcing\n // an expanded menu to a screen reader every time Escape or an outside\n // click dismissed one. The component knows when it closed; nothing else\n // reliably does.\n this.trigger?.classList.remove('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'false');\n }\n\n // Remove document listeners\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n /**\n * Handle document click (close on outside click).\n * @param {MouseEvent} e\n * @private\n */\n _handleDocumentClick(e) {\n if (!this.isOpen) return;\n\n // Don't close if clicking inside dropdown or trigger\n if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {\n return;\n }\n\n this.close();\n }\n\n /**\n * Handle keyboard events.\n * @param {KeyboardEvent} e\n * @private\n */\n _handleKeydown(e) {\n if (e.key === 'Escape') {\n // AND NOBODY ELSE GETS IT. An open dropdown is the innermost thing\n // on screen, so Escape means \"close this\" and nothing further \u2014\n // but the event was left to bubble, and inside a `ManagedWindow`\n // (which binds its own Escape to dismiss) that meant one keystroke\n // closed the dropdown AND the dialog around it. The user loses a\n // form they were filling in because they changed their mind about\n // one field.\n e.preventDefault();\n e.stopPropagation();\n this.close();\n this.trigger?.focus();\n return;\n }\n\n // Arrow key navigation\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n this._navigateOptions(e.key === 'ArrowDown' ? 1 : -1);\n }\n }\n\n /**\n * Navigate options with arrow keys.\n * @param {number} direction - 1 for down, -1 for up\n * @private\n */\n _navigateOptions(direction) {\n const options = this.menuEl?.querySelectorAll('.twm-action-dropdown-option');\n if (!options || options.length === 0) return;\n\n const currentIndex = Array.from(options).findIndex(opt => opt === document.activeElement);\n let nextIndex = currentIndex + direction;\n\n if (nextIndex < 0) nextIndex = options.length - 1;\n if (nextIndex >= options.length) nextIndex = 0;\n\n options[nextIndex]?.focus();\n }\n\n /**\n * Handle option selection.\n * @param {Object} option - The selected option\n * @private\n */\n _selectOption(option) {\n this.close();\n\n if (this.onSelect) {\n this.onSelect(option);\n }\n }\n\n /**\n * Update the options dynamically.\n * @param {Array} newOptions - New options array\n */\n setOptions(newOptions) {\n this.options = newOptions;\n if (this.menuEl) {\n this._renderOptions(this.menuEl);\n }\n }\n\n /**\n * Destroy the dropdown and clean up.\n */\n destroy() {\n this.close();\n\n // Remove trigger listener\n this.trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n\n // Remove menu element\n this.menuEl?.remove();\n this.menuEl = null;\n\n this.trigger = null;\n this.options = [];\n this.onSelect = null;\n }\n\n // =========================================================================\n // STATIC \u2014 shared auto-positioning for any trigger + menu pair\n // =========================================================================\n\n /**\n * Position a dropdown menu relative to a trigger element.\n * Uses `position: fixed` to avoid overflow clipping.\n * Picks above/below based on available space, aligns left/right edge,\n * and applies `max-height` + `overflow-y: auto` when the menu is taller\n * than the available space.\n *\n * @param {HTMLElement} trigger - The element the dropdown opens from\n * @param {HTMLElement} menu - The dropdown menu element to position\n * @param {Object} [opts]\n * @param {'start'|'end'} [opts.align='start'] - Which trigger edge the menu\n * lines up with first: `'start'` its left edge, `'end'` its right edge.\n * Either flips to the other edge when it would leave the window.\n */\n static position(trigger, menu, { align = 'start' } = {}) {\n if (!trigger || !menu) return;\n\n // Reset any prior inline position so natural size can be measured.\n Object.assign(menu.style, {\n position: 'fixed',\n top: 'auto',\n bottom: 'auto',\n left: 'auto',\n right: 'auto',\n maxHeight: '',\n overflowY: ''\n });\n\n const triggerRect = trigger.getBoundingClientRect();\n const menuRect = menu.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // \u2500\u2500 Vertical: prefer below, flip above if insufficient room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;\n const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;\n\n let top;\n let maxHeight;\n\n if (menuRect.height <= spaceBelow) {\n // Fits below \u2014 use natural height.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else if (menuRect.height <= spaceAbove) {\n // Fits above \u2014 use natural height.\n top = triggerRect.top - GAP - menuRect.height;\n maxHeight = spaceAbove;\n } else if (spaceBelow >= spaceAbove) {\n // More space below \u2014 scroll.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else {\n // More space above \u2014 scroll.\n maxHeight = spaceAbove;\n top = EDGE_MARGIN;\n }\n\n // \u2500\u2500 Horizontal: align with the trigger's start (left) or end (right)\n // edge, flip to the other edge if off-screen \u2500\u2500\n let left;\n if (align === 'end') {\n left = triggerRect.right - menuRect.width;\n if (left < EDGE_MARGIN) {\n // Align left edges instead.\n left = triggerRect.left;\n }\n } else {\n left = triggerRect.left;\n if (left + menuRect.width > vw - EDGE_MARGIN) {\n // Align right edges instead.\n left = triggerRect.right - menuRect.width;\n }\n }\n\n // Still off-screen? Clamp to edges.\n left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));\n\n Object.assign(menu.style, {\n position: 'fixed',\n top: `${Math.round(top)}px`,\n left: `${Math.round(left)}px`,\n maxHeight: `${Math.round(maxHeight)}px`,\n overflowY: 'auto'\n });\n }\n}\n\nexport default ActionDropdown;\n", "/**\n * SlideOutPanel\n *\n * Reusable inline slide-out config panel that overlays its host container\n * from the right edge. Used by pages to show element-specific config\n * (plot config, widget config, ETL node config) without the global right panel.\n *\n * Host container must have `position: relative; overflow: hidden`.\n */\nimport { ComponentBase } from '../base/component_base.js';\n\nexport class SlideOutPanel extends ComponentBase {\n\n /** @type {HTMLElement} */ #container = null;\n /** @type {HTMLElement} */ #backdrop = null;\n /** @type {HTMLElement} */ #panel = null;\n /** @type {HTMLElement} */ #headerIcon = null;\n /** @type {HTMLElement} */ #headerTitle = null;\n /** @type {HTMLElement} */ #body = null;\n /** @type {number} */ #width;\n /** @type {Function|null} */#onClose;\n /** @type {boolean} */ #open = false;\n /** @type {ResizeObserver|null} */ #hostObserver = null;\n\n /**\n * @param {object} opts\n * @param {number} [opts.width=380] Panel width in px.\n * @param {Function} [opts.onClose] Called after the panel closes.\n */\n constructor({ width = 700, onClose } = {}) {\n super();\n this.#width = width;\n this.#onClose = onClose ?? null;\n }\n\n // \u2500\u2500\u2500 Lifecycle \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Mount the panel DOM into a host container.\n * @param {HTMLElement} container Must have position:relative; overflow:hidden.\n */\n mount(container) {\n if (this._mounted) return;\n this.#container = container;\n\n // Backdrop (transparent click-catcher)\n this.#backdrop = document.createElement('div');\n this.#backdrop.className = 'twm-slide-out-panel__backdrop';\n this.#backdrop.addEventListener('click', this.#handleBackdropClick);\n\n // Panel\n this.#panel = document.createElement('div');\n this.#panel.className = 'twm-slide-out-panel';\n this.#panel.style.width = `${this.#width}px`;\n\n // Header\n const header = document.createElement('div');\n header.className = 'twm-slide-out-panel__header';\n\n this.#headerIcon = document.createElement('span');\n this.#headerIcon.className = 'twm-slide-out-panel__icon material-symbols-outlined';\n\n this.#headerTitle = document.createElement('span');\n this.#headerTitle.className = 'twm-slide-out-panel__title';\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'twm-slide-out-panel__close twm-btn-icon';\n closeBtn.type = 'button';\n closeBtn.title = 'Close';\n closeBtn.innerHTML = '<span class=\"material-symbols-outlined\">close</span>';\n closeBtn.addEventListener('click', this.#handleCloseClick);\n\n header.append(this.#headerIcon, this.#headerTitle, closeBtn);\n\n // Body (scrollable content area)\n this.#body = document.createElement('div');\n this.#body.className = 'twm-slide-out-panel__body';\n\n this.#panel.append(header, this.#body);\n this.#container.append(this.#backdrop, this.#panel);\n\n this._mounted = true;\n }\n\n dispose() {\n this.close();\n this.#backdrop?.remove();\n this.#panel?.remove();\n this.#backdrop = null;\n this.#panel = null;\n this.#body = null;\n this.#container = null;\n super.dispose();\n }\n\n // \u2500\u2500\u2500 Public API \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\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** The inner content element \u2014 callers render config into this. */\n get contentEl() { return this.#body; }\n\n get isOpen() { return this.#open; }\n\n /**\n * Open (or re-open) the panel with a new title/icon.\n * Clears previous content. Returns contentEl for rendering.\n * @param {string} title\n * @param {string} icon Material Symbols icon name.\n * @returns {HTMLElement} contentEl\n */\n open(title, icon) {\n if (!this._mounted) return null;\n\n this.#body.innerHTML = '';\n this.#headerIcon.textContent = icon || '';\n this.#headerIcon.style.display = icon ? '' : 'none';\n this.#headerTitle.textContent = title || '';\n\n if (!this.#open) {\n this.#open = true;\n this.#backdrop.classList.add('twm-slide-out-panel__backdrop--visible');\n this.#panel.classList.add('twm-slide-out-panel--open');\n document.addEventListener('keydown', this.#handleEsc);\n\n // The host may be a scroll container \u2014 absolutely-positioned\n // descendants of one DO scroll along with the content\n // (Web spec: a position:absolute element is clipped + offset\n // by its containing scroll context). We escape that by\n // switching the panel to `position: fixed` while open and\n // tracking the host's bounding rect on scroll / resize, so\n // the panel stays pinned to the host's visible frame at\n // full height regardless of how the user scrolls inside.\n this.#trackGeometry();\n window.addEventListener('scroll', this.#trackGeometry, true);\n window.addEventListener('resize', this.#trackGeometry);\n this.#observeHost();\n }\n\n return this.#body;\n }\n\n /** Close the panel and clear content. */\n close() {\n if (!this.#open) return;\n this.#open = false;\n\n this.#panel.classList.remove('twm-slide-out-panel--open');\n this.#backdrop.classList.remove('twm-slide-out-panel__backdrop--visible');\n document.removeEventListener('keydown', this.#handleEsc);\n window.removeEventListener('scroll', this.#trackGeometry, true);\n window.removeEventListener('resize', this.#trackGeometry);\n this.#hostObserver?.disconnect();\n this.#hostObserver = null;\n // Leave the inline position:fixed geometry in place. The panel\n // slides off-screen via the transform animation (translateX\n // 100%); the next open() re-runs #trackGeometry which will\n // refresh top/left/height from the host's current rect.\n\n this.#body.innerHTML = '';\n this.#onClose?.();\n }\n\n // \u2500\u2500\u2500 Event handlers (arrow fns for stable `this`) \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 #handleBackdropClick = () => { this.close(); };\n\n #handleCloseClick = () => { this.close(); };\n\n #handleEsc = (e) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n this.close();\n }\n };\n\n /** The host moves or resizes without any window event when a tile\n * splitter is dragged or a dock panel toggles, and the fixed geometry\n * would then stay at the old coordinates. Observe the host AND each\n * ancestor: a host can move without changing size of its own (a dock\n * panel toggling beside a fixed-width tile shrinks the content area,\n * an ancestor, and the tile only shifts). The callback runs\n * synchronously, after layout and before paint, so the panel follows\n * a drag without a frame of lag; it only writes the panel's own\n * `position: fixed` geometry, which is out of flow and resizes\n * nothing observed, so it cannot loop. */\n #observeHost() {\n this.#hostObserver = new ResizeObserver(this.#trackGeometry);\n for (let el = this.#container; el; el = el.parentElement) {\n this.#hostObserver.observe(el);\n }\n }\n\n /** Pin the panel to the right edge of its host's *visible* frame\n * using `position: fixed`. Re-run on every window scroll/resize and\n * every host resize (#observeHost) so the panel always overlays the\n * host correctly even when the host itself sits inside a scrolled\n * ancestor.\n *\n * UNDER A CSS `zoom` (the shell's content zoom scales a tile body),\n * `getBoundingClientRect()` answers in screen pixels, which already\n * carry the zoom, while the panel's own `top`/`left`/`height` are\n * CSS pixels that the same zoom multiplies again. Copied across\n * unchanged, every length was scaled twice: at 150% the panel landed\n * right of and below its host, at 50% it was half the host's height.\n * So the rect is divided by the panel's effective zoom\n * (`currentCSSZoom`: 1 when nothing above it zooms, `undefined` in an\n * engine that predates it), and the width subtracted is the panel's\n * laid-out one, which `min-width` may hold above the requested\n * width. Rounding happens in screen pixels, so at 100% the output is\n * exactly what it always was.\n *\n * NEVER WIDER THAN THE HOST (BUG-0035). The right edge is pinned, so a\n * panel wider than its host (700px by default, `min-width: 400px`)\n * reached past the host's left edge across the neighbouring pane. The\n * host's width, in the panel's CSS pixels, goes on the panel as\n * `--twm-slide-out-host-w`, which the stylesheet uses as `max-width`\n * and as the ceiling of the 400px `min-width`; it is set BEFORE the\n * width is read so the read sees the capped layout, and the width is\n * capped here too for an engine that lays nothing out. */\n #trackGeometry = () => {\n if (!this.#open || !this.#panel || !this.#container) return;\n const r = this.#container.getBoundingClientRect();\n const zoom = this.#panel.currentCSSZoom || 1;\n const hostWidth = r.width / zoom;\n const s = this.#panel.style;\n s.setProperty('--twm-slide-out-host-w', `${hostWidth}px`);\n const width = Math.min(this.#panel.offsetWidth || this.#width, hostWidth);\n s.position = 'fixed';\n s.top = `${Math.round(r.top) / zoom}px`;\n s.left = `${Math.round(r.right - width * zoom) / zoom}px`;\n s.right = 'auto';\n s.height = `${Math.round(r.height) / zoom}px`;\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
File without changes
File without changes