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
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-N7HC3ADV.js";
5
5
  import {
6
6
  DragReorder
7
- } from "./chunk-WVFGV5FT.js";
7
+ } from "./chunk-WUFLGETN.js";
8
8
 
9
9
  // src/ui/components/tabs.js
10
10
  var TAB_LEVELS = Object.freeze(["info", "warning", "alert"]);
@@ -372,4 +372,4 @@ export {
372
372
  TAB_LEVELS,
373
373
  createTabs
374
374
  };
375
- //# sourceMappingURL=chunk-HYJYYEPK.js.map
375
+ //# sourceMappingURL=chunk-REJ5HLIM.js.map
@@ -199,7 +199,44 @@ var DragReorder = class {
199
199
  }
200
200
  };
201
201
 
202
+ // src/ui/utils/menu_viewport.js
203
+ var MENU_VIEWPORT_MARGIN = 8;
204
+ function placeMenuInViewport(menu, x, y, margin = MENU_VIEWPORT_MARGIN) {
205
+ const view = menu.ownerDocument?.defaultView || window;
206
+ const vw = view.innerWidth;
207
+ const vh = view.innerHeight;
208
+ const maxHeight = Math.max(0, vh - 2 * margin);
209
+ menu.style.maxHeight = `${maxHeight}px`;
210
+ menu.scrollTop = 0;
211
+ const rect = menu.getBoundingClientRect();
212
+ const left = Math.min(x, vw - rect.width - margin);
213
+ const top = Math.min(y, vh - rect.height - margin);
214
+ menu.style.left = `${Math.max(0, left)}px`;
215
+ menu.style.top = `${Math.max(0, top)}px`;
216
+ }
217
+ function focusMenuItem(item) {
218
+ if (!item) return;
219
+ item.focus({ preventScroll: true });
220
+ item.scrollIntoView?.({ block: "nearest", inline: "nearest" });
221
+ }
222
+ function moveMenuFocus(e, items) {
223
+ if (!items.length) return false;
224
+ const at = items.indexOf(items[0].ownerDocument.activeElement);
225
+ let next = null;
226
+ if (e.key === "ArrowDown") next = items[(at + 1 + items.length) % items.length];
227
+ else if (e.key === "ArrowUp") next = items[(at - 1 + items.length) % items.length];
228
+ else if (e.key === "Home") next = items[0];
229
+ else if (e.key === "End") next = items[items.length - 1];
230
+ if (!next) return false;
231
+ e.preventDefault();
232
+ focusMenuItem(next);
233
+ return true;
234
+ }
235
+
202
236
  export {
203
- DragReorder
237
+ DragReorder,
238
+ placeMenuInViewport,
239
+ focusMenuItem,
240
+ moveMenuFocus
204
241
  };
205
- //# sourceMappingURL=chunk-WVFGV5FT.js.map
242
+ //# sourceMappingURL=chunk-WUFLGETN.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/ui/components/drag_reorder.js"],
4
- "sourcesContent": ["/**\n * DragReorder \u2014 the one same-list drag-to-reorder implementation, with\n * the \"classic\" ghost (`.dragging` on the dragged element) + drop\n * placeholder (an indicator div inserted at the drop position) and\n * edge auto-scroll. Native HTML5 drag-and-drop.\n *\n * Extracted from the duplicated tab-bar reorder code so the notebook tab\n * bar, the workspace tab bar, and the schema column strip all share one\n * implementation. Axis-aware (`x` for horizontal strips, `y` for\n * vertical lists) and parameterised so it makes no assumptions about\n * tabs specifically.\n *\n * Usage:\n * const dr = new DragReorder({\n * container, itemSelector: '.tab', keyAttr: 'tabId', axis: 'x',\n * onReorder: (order) => { ... }, // order = array of keys\n * });\n * dr.attach(); // after each (re)render of the items\n * dr.destroy(); // on teardown\n *\n * Coexistence: the container-level handlers no-op unless THIS instance's\n * drag is active, so a host can keep its own dragover/drop handlers for\n * other concerns (e.g. cross-pane tab drops) alongside it \u2014 gate those\n * on `dr.isActive() === false`.\n */\nexport class DragReorder {\n /**\n * @param {Object} opts\n * @param {HTMLElement} opts.container\n * @param {string} [opts.itemSelector='.tab'] selector for reorderable items\n * @param {string} [opts.keyAttr='path'] dataset key / data-attr identifying an item\n * @param {'x'|'y'} [opts.axis='x']\n * @param {string} [opts.indicatorClass='tab-drop-indicator']\n * @param {boolean} [opts.autoScroll=true]\n * @param {(e: DragEvent, item: HTMLElement) => boolean} [opts.draggableGuard]\n * return false to cancel a drag start (e.g. from a close button)\n * @param {(e: DragEvent, key: string, item: HTMLElement) => void} [opts.onDragStart]\n * host hook to augment dataTransfer (e.g. add a cross-pane MIME)\n * @param {(order: string[]) => void} [opts.onReorder]\n */\n constructor(opts = {}) {\n this.container = opts.container || null;\n this.itemSelector = opts.itemSelector || '.tab';\n this.keyAttr = opts.keyAttr || 'path';\n this.axis = opts.axis === 'y' ? 'y' : 'x';\n this.indicatorClass = opts.indicatorClass || 'twm-tab-drop-indicator';\n this.autoScroll = opts.autoScroll !== false;\n this.draggableGuard = opts.draggableGuard || null;\n this.onDragStart = opts.onDragStart || null;\n this.onReorder = opts.onReorder || null;\n\n this._draggingEl = null;\n this._indicatorEl = null;\n this._lastSnapshot = [];\n\n this._onOver = this._onOver.bind(this);\n this._onDrop = this._onDrop.bind(this);\n this._onLeave = this._onLeave.bind(this);\n if (this.container) {\n this.container.addEventListener('dragover', this._onOver);\n this.container.addEventListener('drop', this._onDrop);\n this.container.addEventListener('dragleave', this._onLeave);\n }\n }\n\n /** Mark current items draggable + wire dragstart/dragend. Idempotent \u2014\n * call after every (re)render; already-wired items are skipped. */\n attach() {\n if (!this.container) return;\n for (const item of this.container.querySelectorAll(this.itemSelector)) {\n if (item.__dragReorder) continue;\n item.__dragReorder = true;\n item.setAttribute('draggable', 'true');\n item.addEventListener('dragstart', (e) => this._start(e, item));\n item.addEventListener('dragend', () => this._end());\n }\n }\n\n /** True while this instance is mid-drag. */\n isActive() { return !!this._draggingEl; }\n\n destroy() {\n if (this.container) {\n this.container.removeEventListener('dragover', this._onOver);\n this.container.removeEventListener('drop', this._onDrop);\n this.container.removeEventListener('dragleave', this._onLeave);\n }\n this._removeIndicator();\n this._draggingEl = null;\n }\n\n // \u2500\u2500 internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _key(el) {\n if (!el) return null;\n return el.dataset?.[this.keyAttr]\n ?? el.getAttribute?.(`data-${this.keyAttr}`)\n ?? null;\n }\n\n _items() {\n return Array.from(this.container.querySelectorAll(this.itemSelector));\n }\n\n _snapshot() {\n return this._items().map((el) => this._key(el)).filter((k) => k != null);\n }\n\n _centerOf(rect) {\n return this.axis === 'y'\n ? rect.top + rect.height / 2\n : rect.left + rect.width / 2;\n }\n\n _start(e, item) {\n if (this.draggableGuard && !this.draggableGuard(e, item)) {\n e.preventDefault();\n return;\n }\n const key = this._key(item);\n this._draggingEl = item;\n this._lastSnapshot = this._snapshot();\n item.classList.add('dragging');\n try {\n e.dataTransfer.effectAllowed = 'move';\n if (key != null) e.dataTransfer.setData('text/plain', String(key));\n } catch { /* ignore */ }\n this.onDragStart?.(e, key, item);\n this._ensureIndicator();\n this._positionByNearest(this._centerOf(item.getBoundingClientRect()));\n }\n\n _end() {\n if (!this._draggingEl) return;\n this._draggingEl.classList.remove('dragging');\n this._draggingEl = null;\n this._removeIndicator();\n this._commitIfChanged();\n }\n\n _onOver(e) {\n if (!this._draggingEl) return; // not our drag \u2014 let the host handle it\n e.preventDefault();\n try { e.dataTransfer.dropEffect = 'move'; } catch { /* ignore */ }\n const coord = this.axis === 'y' ? e.clientY : e.clientX;\n this._positionByNearest(coord);\n if (this.autoScroll) this._autoScrollEdge(coord);\n }\n\n _onDrop(e) {\n if (!this._draggingEl) return;\n e.preventDefault();\n const indicator = this._ensureIndicator();\n if (indicator?.parentElement) {\n this.container.insertBefore(this._draggingEl, indicator);\n }\n this._finalize();\n }\n\n _onLeave(e) {\n if (!this._draggingEl) return;\n if (e.currentTarget !== this.container) return;\n if (this.container.contains(e.relatedTarget)) return;\n this._finalize();\n }\n\n _finalize() {\n if (this._draggingEl) this._draggingEl.classList.remove('dragging');\n this._removeIndicator();\n this._draggingEl = null;\n this._commitIfChanged();\n }\n\n _commitIfChanged() {\n const order = this._snapshot();\n if (!order.length) return;\n if (this._lastSnapshot.join('\u0000') === order.join('\u0000')) return;\n this._lastSnapshot = order;\n this.onReorder?.(order);\n }\n\n _ensureIndicator() {\n if (!this._indicatorEl) {\n this._indicatorEl = document.createElement('div');\n this._indicatorEl.className = this.indicatorClass;\n this._indicatorEl.setAttribute('aria-hidden', 'true');\n }\n if (!this._indicatorEl.parentElement) {\n this.container.appendChild(this._indicatorEl);\n }\n return this._indicatorEl;\n }\n\n _removeIndicator() { this._indicatorEl?.remove(); }\n\n _positionByNearest(coord) {\n if (!this.container || !this._indicatorEl || !this._draggingEl) return;\n const items = this._items().filter((t) => t !== this._draggingEl);\n if (!items.length) return;\n let best = null;\n let bestDist = Infinity;\n for (const it of items) {\n const center = this._centerOf(it.getBoundingClientRect());\n const dist = Math.abs(coord - center);\n if (dist < bestDist) { bestDist = dist; best = { it, center }; }\n }\n if (!best) return;\n const after = coord >= best.center;\n const ref = after ? best.it.nextElementSibling : best.it;\n this.container.insertBefore(this._indicatorEl, ref || null);\n }\n\n _autoScrollEdge(coord) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n const edge = 24;\n const step = 16;\n if (this.axis === 'y') {\n if (coord - rect.top < edge) {\n this.container.scrollTop = Math.max(0, this.container.scrollTop - step);\n } else if (rect.bottom - coord < edge) {\n this.container.scrollTop = Math.min(\n this.container.scrollHeight, this.container.scrollTop + step);\n }\n } else {\n if (coord - rect.left < edge) {\n this.container.scrollLeft = Math.max(0, this.container.scrollLeft - step);\n } else if (rect.right - coord < edge) {\n this.container.scrollLeft = Math.min(\n this.container.scrollWidth, this.container.scrollLeft + step);\n }\n }\n }\n}\n"],
5
- "mappings": "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",
3
+ "sources": ["../src/ui/components/drag_reorder.js", "../src/ui/utils/menu_viewport.js"],
4
+ "sourcesContent": ["/**\n * DragReorder \u2014 the one same-list drag-to-reorder implementation, with\n * the \"classic\" ghost (`.dragging` on the dragged element) + drop\n * placeholder (an indicator div inserted at the drop position) and\n * edge auto-scroll. Native HTML5 drag-and-drop.\n *\n * Extracted from the duplicated tab-bar reorder code so the notebook tab\n * bar, the workspace tab bar, and the schema column strip all share one\n * implementation. Axis-aware (`x` for horizontal strips, `y` for\n * vertical lists) and parameterised so it makes no assumptions about\n * tabs specifically.\n *\n * Usage:\n * const dr = new DragReorder({\n * container, itemSelector: '.tab', keyAttr: 'tabId', axis: 'x',\n * onReorder: (order) => { ... }, // order = array of keys\n * });\n * dr.attach(); // after each (re)render of the items\n * dr.destroy(); // on teardown\n *\n * Coexistence: the container-level handlers no-op unless THIS instance's\n * drag is active, so a host can keep its own dragover/drop handlers for\n * other concerns (e.g. cross-pane tab drops) alongside it \u2014 gate those\n * on `dr.isActive() === false`.\n */\nexport class DragReorder {\n /**\n * @param {Object} opts\n * @param {HTMLElement} opts.container\n * @param {string} [opts.itemSelector='.tab'] selector for reorderable items\n * @param {string} [opts.keyAttr='path'] dataset key / data-attr identifying an item\n * @param {'x'|'y'} [opts.axis='x']\n * @param {string} [opts.indicatorClass='tab-drop-indicator']\n * @param {boolean} [opts.autoScroll=true]\n * @param {(e: DragEvent, item: HTMLElement) => boolean} [opts.draggableGuard]\n * return false to cancel a drag start (e.g. from a close button)\n * @param {(e: DragEvent, key: string, item: HTMLElement) => void} [opts.onDragStart]\n * host hook to augment dataTransfer (e.g. add a cross-pane MIME)\n * @param {(order: string[]) => void} [opts.onReorder]\n */\n constructor(opts = {}) {\n this.container = opts.container || null;\n this.itemSelector = opts.itemSelector || '.tab';\n this.keyAttr = opts.keyAttr || 'path';\n this.axis = opts.axis === 'y' ? 'y' : 'x';\n this.indicatorClass = opts.indicatorClass || 'twm-tab-drop-indicator';\n this.autoScroll = opts.autoScroll !== false;\n this.draggableGuard = opts.draggableGuard || null;\n this.onDragStart = opts.onDragStart || null;\n this.onReorder = opts.onReorder || null;\n\n this._draggingEl = null;\n this._indicatorEl = null;\n this._lastSnapshot = [];\n\n this._onOver = this._onOver.bind(this);\n this._onDrop = this._onDrop.bind(this);\n this._onLeave = this._onLeave.bind(this);\n if (this.container) {\n this.container.addEventListener('dragover', this._onOver);\n this.container.addEventListener('drop', this._onDrop);\n this.container.addEventListener('dragleave', this._onLeave);\n }\n }\n\n /** Mark current items draggable + wire dragstart/dragend. Idempotent \u2014\n * call after every (re)render; already-wired items are skipped. */\n attach() {\n if (!this.container) return;\n for (const item of this.container.querySelectorAll(this.itemSelector)) {\n if (item.__dragReorder) continue;\n item.__dragReorder = true;\n item.setAttribute('draggable', 'true');\n item.addEventListener('dragstart', (e) => this._start(e, item));\n item.addEventListener('dragend', () => this._end());\n }\n }\n\n /** True while this instance is mid-drag. */\n isActive() { return !!this._draggingEl; }\n\n destroy() {\n if (this.container) {\n this.container.removeEventListener('dragover', this._onOver);\n this.container.removeEventListener('drop', this._onDrop);\n this.container.removeEventListener('dragleave', this._onLeave);\n }\n this._removeIndicator();\n this._draggingEl = null;\n }\n\n // \u2500\u2500 internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n _key(el) {\n if (!el) return null;\n return el.dataset?.[this.keyAttr]\n ?? el.getAttribute?.(`data-${this.keyAttr}`)\n ?? null;\n }\n\n _items() {\n return Array.from(this.container.querySelectorAll(this.itemSelector));\n }\n\n _snapshot() {\n return this._items().map((el) => this._key(el)).filter((k) => k != null);\n }\n\n _centerOf(rect) {\n return this.axis === 'y'\n ? rect.top + rect.height / 2\n : rect.left + rect.width / 2;\n }\n\n _start(e, item) {\n if (this.draggableGuard && !this.draggableGuard(e, item)) {\n e.preventDefault();\n return;\n }\n const key = this._key(item);\n this._draggingEl = item;\n this._lastSnapshot = this._snapshot();\n item.classList.add('dragging');\n try {\n e.dataTransfer.effectAllowed = 'move';\n if (key != null) e.dataTransfer.setData('text/plain', String(key));\n } catch { /* ignore */ }\n this.onDragStart?.(e, key, item);\n this._ensureIndicator();\n this._positionByNearest(this._centerOf(item.getBoundingClientRect()));\n }\n\n _end() {\n if (!this._draggingEl) return;\n this._draggingEl.classList.remove('dragging');\n this._draggingEl = null;\n this._removeIndicator();\n this._commitIfChanged();\n }\n\n _onOver(e) {\n if (!this._draggingEl) return; // not our drag \u2014 let the host handle it\n e.preventDefault();\n try { e.dataTransfer.dropEffect = 'move'; } catch { /* ignore */ }\n const coord = this.axis === 'y' ? e.clientY : e.clientX;\n this._positionByNearest(coord);\n if (this.autoScroll) this._autoScrollEdge(coord);\n }\n\n _onDrop(e) {\n if (!this._draggingEl) return;\n e.preventDefault();\n const indicator = this._ensureIndicator();\n if (indicator?.parentElement) {\n this.container.insertBefore(this._draggingEl, indicator);\n }\n this._finalize();\n }\n\n _onLeave(e) {\n if (!this._draggingEl) return;\n if (e.currentTarget !== this.container) return;\n if (this.container.contains(e.relatedTarget)) return;\n this._finalize();\n }\n\n _finalize() {\n if (this._draggingEl) this._draggingEl.classList.remove('dragging');\n this._removeIndicator();\n this._draggingEl = null;\n this._commitIfChanged();\n }\n\n _commitIfChanged() {\n const order = this._snapshot();\n if (!order.length) return;\n if (this._lastSnapshot.join('\u0000') === order.join('\u0000')) return;\n this._lastSnapshot = order;\n this.onReorder?.(order);\n }\n\n _ensureIndicator() {\n if (!this._indicatorEl) {\n this._indicatorEl = document.createElement('div');\n this._indicatorEl.className = this.indicatorClass;\n this._indicatorEl.setAttribute('aria-hidden', 'true');\n }\n if (!this._indicatorEl.parentElement) {\n this.container.appendChild(this._indicatorEl);\n }\n return this._indicatorEl;\n }\n\n _removeIndicator() { this._indicatorEl?.remove(); }\n\n _positionByNearest(coord) {\n if (!this.container || !this._indicatorEl || !this._draggingEl) return;\n const items = this._items().filter((t) => t !== this._draggingEl);\n if (!items.length) return;\n let best = null;\n let bestDist = Infinity;\n for (const it of items) {\n const center = this._centerOf(it.getBoundingClientRect());\n const dist = Math.abs(coord - center);\n if (dist < bestDist) { bestDist = dist; best = { it, center }; }\n }\n if (!best) return;\n const after = coord >= best.center;\n const ref = after ? best.it.nextElementSibling : best.it;\n this.container.insertBefore(this._indicatorEl, ref || null);\n }\n\n _autoScrollEdge(coord) {\n if (!this.container) return;\n const rect = this.container.getBoundingClientRect();\n const edge = 24;\n const step = 16;\n if (this.axis === 'y') {\n if (coord - rect.top < edge) {\n this.container.scrollTop = Math.max(0, this.container.scrollTop - step);\n } else if (rect.bottom - coord < edge) {\n this.container.scrollTop = Math.min(\n this.container.scrollHeight, this.container.scrollTop + step);\n }\n } else {\n if (coord - rect.left < edge) {\n this.container.scrollLeft = Math.max(0, this.container.scrollLeft - step);\n } else if (rect.right - coord < edge) {\n this.container.scrollLeft = Math.min(\n this.container.scrollWidth, this.container.scrollLeft + step);\n }\n }\n }\n}\n", "/**\n * menu_viewport.js \u2014 keeping a context menu inside the window (BUG-0040).\n *\n * FlexDesk draws three context menus of its own: `showContextMenu`\n * (../components/context_menu.js), the DataTable's right-click menu\n * (../components/data_table.js) and `NotebookTabBar`'s tab menu\n * (../../editor/notebook_tab_bar.js). Each placed itself with its own copy of\n * `top = min(y, innerHeight - height - 8)`, and none had a max-height. A menu\n * taller than the window then opened above its top edge (the DataTable's did\n * not clamp at all: a 101-item row menu opened at y=-2153 in a 761 px window)\n * or ran off its bottom, and nothing scrolled, so the items past the edge could\n * not be reached by any means.\n *\n * One placement now, for all three:\n * - the menu is at most the window's height less a margin above and below,\n * and scrolls inside when it is longer (`overflow-y: auto` in flexdesk.css);\n * - it opens at the pointer, moved left or up just enough to fit, and never\n * above the window's top or left edge;\n * - keyboard movement focuses an item and scrolls it into view within the\n * menu, so every item can be reached with the arrows.\n */\n\n/** The gap kept between a menu and the window's edges, in px. */\nexport const MENU_VIEWPORT_MARGIN = 8;\n\n/**\n * Size and place an open (displayed) menu at `(x, y)` inside its window.\n *\n * @param {HTMLElement} menu the menu, already in the document and displayed\n * @param {number} x client x of the pointer\n * @param {number} y client y of the pointer\n * @param {number} [margin] gap to the window's edges\n */\nexport function placeMenuInViewport(menu, x, y, margin = MENU_VIEWPORT_MARGIN) {\n const view = menu.ownerDocument?.defaultView || window;\n const vw = view.innerWidth;\n const vh = view.innerHeight;\n const maxHeight = Math.max(0, vh - 2 * margin);\n menu.style.maxHeight = `${maxHeight}px`;\n menu.scrollTop = 0;\n // flexdesk.css makes the menu `box-sizing: border-box`, so its measured\n // height (padding and border included) is at most `maxHeight`.\n const rect = menu.getBoundingClientRect();\n const left = Math.min(x, vw - rect.width - margin);\n const top = Math.min(y, vh - rect.height - margin);\n menu.style.left = `${Math.max(0, left)}px`;\n menu.style.top = `${Math.max(0, top)}px`;\n}\n\n/**\n * Focus a menu item and bring it into view inside the (scrolling) menu.\n * `preventScroll` keeps the page where it is; `block: 'nearest'` scrolls the\n * menu only as far as the item needs.\n */\nexport function focusMenuItem(item) {\n if (!item) return;\n item.focus({ preventScroll: true });\n item.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });\n}\n\n/**\n * Up/Down/Home/End over `items` (the enabled ones), wrapping, from the one\n * that has focus. Returns true when the key moved focus (the caller's event\n * has then been `preventDefault`ed).\n *\n * @param {KeyboardEvent} e\n * @param {HTMLElement[]} items\n */\nexport function moveMenuFocus(e, items) {\n if (!items.length) return false;\n const at = items.indexOf(items[0].ownerDocument.activeElement);\n let next = null;\n if (e.key === 'ArrowDown') next = items[(at + 1 + items.length) % items.length];\n else if (e.key === 'ArrowUp') next = items[(at - 1 + items.length) % items.length];\n else if (e.key === 'Home') next = items[0];\n else if (e.key === 'End') next = items[items.length - 1];\n if (!next) return false;\n e.preventDefault();\n focusMenuItem(next);\n return true;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/core.js CHANGED
File without changes
package/dist/core.js.map CHANGED
File without changes
package/dist/editor.js CHANGED
@@ -3,9 +3,9 @@ import {
3
3
  NotebookTabBar,
4
4
  getTabDragMime,
5
5
  setTabDragMime
6
- } from "./chunk-UURZ37SD.js";
6
+ } from "./chunk-7U4IB65H.js";
7
7
  import "./chunk-N7HC3ADV.js";
8
- import "./chunk-WVFGV5FT.js";
8
+ import "./chunk-WUFLGETN.js";
9
9
  import "./chunk-JYWURG5T.js";
10
10
 
11
11
  // src/editor/editor_pane.js
File without changes
package/dist/flexdesk.css CHANGED
@@ -3959,11 +3959,14 @@ body.twm-window-maximized .twm-resize-handle {
3959
3959
  position: relative;
3960
3960
  }
3961
3961
 
3962
+ /* While open the menu is moved to document.body and placed `position: fixed`
3963
+ by `ActionDropdown.position` (inline styles), so a narrow tile cannot clip
3964
+ it; --z-over-windows keeps it over a managed window holding the grid. */
3962
3965
  .tile-menu-dropdown {
3963
3966
  position: absolute;
3964
3967
  top: 100%;
3965
3968
  right: 0;
3966
- z-index: var(--z-dropdown);
3969
+ z-index: var(--z-over-windows);
3967
3970
  min-width: 180px;
3968
3971
  padding: var(--space-xs) 0;
3969
3972
  background: var(--surface-2);
@@ -7629,6 +7632,23 @@ body.twm-dragging * { user-select: none; }
7629
7632
  transition: opacity 90ms ease-out;
7630
7633
  }
7631
7634
  .nb-context-menu.visible { opacity: 1; }
7635
+
7636
+ /* ── Context menus stay inside the window (0.17.9, BUG-0040) ─────────
7637
+ * `showContextMenu`, the DataTable's right-click menu and NotebookTabBar's tab
7638
+ * menu are placed by src/ui/utils/menu_viewport.js, which sets an inline
7639
+ * max-height of the window's height less a margin top and bottom. Here: the
7640
+ * same cap for a menu placed before that runs, border-box so the cap includes
7641
+ * padding and border, and scrolling inside when the items are longer — they
7642
+ * had no max-height and no overflow, so a 100-item menu ran off the window and
7643
+ * its far items could not be reached. */
7644
+ .twm-context-menu,
7645
+ .nb-context-menu {
7646
+ box-sizing: border-box;
7647
+ max-height: calc(100vh - 2 * var(--space-md));
7648
+ overflow-x: hidden;
7649
+ overflow-y: auto;
7650
+ overscroll-behavior: contain;
7651
+ }
7632
7652
  .nb-context-menu__item {
7633
7653
  display: flex;
7634
7654
  align-items: center;
@@ -10808,8 +10828,8 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
10808
10828
  /* ══ SERIES TRANSFORMS ═══════════════════════════════════════════════
10809
10829
  *
10810
10830
  * `renderSeriesTransformControls` (src/charts/series_transforms.js): per
10811
- * injected transform, a group with a toggle per output and a number field per
10812
- * parameter, in a subplot's config. */
10831
+ * injected transform, a group with a toggle per output and a number field (or,
10832
+ * for a choice parameter, a select) per parameter, in a subplot's config. */
10813
10833
  .twm-series-transforms {
10814
10834
  display: flex;
10815
10835
  flex-direction: column;
@@ -10849,6 +10869,13 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
10849
10869
  .twm-series-transforms__param:disabled {
10850
10870
  opacity: 0.5;
10851
10871
  }
10872
+ /* A choice parameter's select (0.17.8): as wide as its option labels. */
10873
+ .twm-series-transforms__select {
10874
+ width: auto;
10875
+ min-width: 96px;
10876
+ max-width: 60%;
10877
+ cursor: pointer;
10878
+ }
10852
10879
  /* ══ end SERIES TRANSFORMS ══ */
10853
10880
 
10854
10881
  /* ══ PLOT GRID ════════════════════════════════════════════════════════
package/dist/flow.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  modalHost
3
- } from "./chunk-D3COLZZ2.js";
3
+ } from "./chunk-BE2PU3LL.js";
4
4
  import {
5
5
  DataTable
6
- } from "./chunk-LWV23YVE.js";
6
+ } from "./chunk-NIJEJAZ7.js";
7
7
  import {
8
8
  choiceList,
9
9
  selectBox,
@@ -11,7 +11,7 @@ import {
11
11
  toggleChips
12
12
  } from "./chunk-CVMCZZX2.js";
13
13
  import "./chunk-HNU6MM2B.js";
14
- import "./chunk-WVFGV5FT.js";
14
+ import "./chunk-WUFLGETN.js";
15
15
  import {
16
16
  CONNECTOR_STRINGS,
17
17
  CanvasAdapter,
package/dist/flow.js.map CHANGED
File without changes
package/dist/host.js CHANGED
File without changes
package/dist/host.js.map CHANGED
File without changes
package/dist/reset.css CHANGED
File without changes
File without changes
File without changes
package/dist/tiles.js CHANGED
@@ -14,13 +14,14 @@ import {
14
14
  import {
15
15
  ActionDropdown,
16
16
  SlideOutPanel
17
- } from "./chunk-RKNFBX5K.js";
17
+ } from "./chunk-MIW7D6WC.js";
18
18
  import {
19
19
  showContextMenu
20
- } from "./chunk-UWO43BZU.js";
20
+ } from "./chunk-F35XSQWA.js";
21
21
  import {
22
+ modalHost,
22
23
  openModal
23
- } from "./chunk-D3COLZZ2.js";
24
+ } from "./chunk-BE2PU3LL.js";
24
25
  import {
25
26
  buildHistogramTrace,
26
27
  buildLegendLayout,
@@ -35,13 +36,13 @@ import {
35
36
  purge,
36
37
  resize,
37
38
  strideDownsampleShared
38
- } from "./chunk-RMTRWWEO.js";
39
- import "./chunk-LWV23YVE.js";
39
+ } from "./chunk-2YTM3GJE.js";
40
+ import "./chunk-NIJEJAZ7.js";
40
41
  import {
41
42
  isHexColor
42
43
  } from "./chunk-CVMCZZX2.js";
43
44
  import "./chunk-HNU6MM2B.js";
44
- import "./chunk-WVFGV5FT.js";
45
+ import "./chunk-WUFLGETN.js";
45
46
  import {
46
47
  __toCommonJS
47
48
  } from "./chunk-JYWURG5T.js";
@@ -514,6 +515,10 @@ var TileBase = class {
514
515
  dispose() {
515
516
  if (this._disposed) return;
516
517
  this._disposed = true;
518
+ this._closeTileMenu();
519
+ this._menuButton = null;
520
+ this._menuDropdown = null;
521
+ this._menuHome = null;
517
522
  if (this._expandWindow?.isVisible) {
518
523
  this._expandWindow.close();
519
524
  }
@@ -594,6 +599,10 @@ var TileBase = class {
594
599
  * @private
595
600
  */
596
601
  _buildChrome() {
602
+ this._closeTileMenu();
603
+ this._menuButton = null;
604
+ this._menuDropdown = null;
605
+ this._menuHome = null;
597
606
  const header = document.createElement("div");
598
607
  header.className = "tile-header";
599
608
  const expandBtnHtml = this.constructor.EXPANDABLE ? `<button class="tile-expand-btn twm-has-tooltip" data-tooltip="Open in window">
@@ -663,26 +672,18 @@ var TileBase = class {
663
672
  const menuBtn = header.querySelector(".tile-menu-btn");
664
673
  const menuDropdown = header.querySelector(".tile-menu-dropdown");
665
674
  if (menuBtn && menuDropdown) {
675
+ this._menuButton = menuBtn;
676
+ this._menuDropdown = menuDropdown;
677
+ this._menuHome = menuDropdown.parentNode;
666
678
  menuBtn.addEventListener("click", (e) => {
667
679
  e.stopPropagation();
668
- const isOpen = !menuDropdown.hidden;
669
- menuDropdown.hidden = !menuDropdown.hidden;
670
- if (!isOpen) {
671
- const closeMenu = (ev) => {
672
- if (!menuDropdown.contains(ev.target) && ev.target !== menuBtn) {
673
- menuDropdown.hidden = true;
674
- document.removeEventListener("pointerdown", closeMenu, true);
675
- }
676
- };
677
- requestAnimationFrame(() => {
678
- document.addEventListener("pointerdown", closeMenu, true);
679
- });
680
- }
680
+ if (menuDropdown.hidden) this._openTileMenu();
681
+ else this._closeTileMenu();
681
682
  });
682
683
  menuDropdown.querySelectorAll(".tile-menu-item").forEach((itemEl) => {
683
684
  itemEl.addEventListener("click", (e) => {
684
685
  e.stopPropagation();
685
- menuDropdown.hidden = true;
686
+ this._closeTileMenu();
686
687
  this._onMenuAction(itemEl.dataset.action);
687
688
  });
688
689
  });
@@ -716,6 +717,48 @@ var TileBase = class {
716
717
  });
717
718
  }
718
719
  }
720
+ /**
721
+ * Open the ⋯ menu. It is drawn on `document.body` (or the open modal's
722
+ * host, as `showContextMenu` is), not inside the tile: a tile clips its
723
+ * content, so a menu wider than a narrow tile lost its left part. It is
724
+ * placed with `ActionDropdown.position` — fixed, under the button (above
725
+ * when there is no room), right edges aligned, kept inside the window.
726
+ * @private
727
+ */
728
+ _openTileMenu() {
729
+ const btn = this._menuButton;
730
+ const menu = this._menuDropdown;
731
+ if (!btn || !menu || !menu.hidden) return;
732
+ (modalHost() || document.body).appendChild(menu);
733
+ menu.hidden = false;
734
+ ActionDropdown.position(btn, menu, { align: "end" });
735
+ this._menuOutsideListener = (ev) => {
736
+ if (!menu.contains(ev.target) && !btn.contains(ev.target)) this._closeTileMenu();
737
+ };
738
+ const listener = this._menuOutsideListener;
739
+ requestAnimationFrame(() => {
740
+ if (this._menuOutsideListener === listener) {
741
+ document.addEventListener("pointerdown", listener, true);
742
+ }
743
+ });
744
+ }
745
+ /**
746
+ * Close the ⋯ menu and put it back in the tile's header.
747
+ * @private
748
+ */
749
+ _closeTileMenu() {
750
+ const menu = this._menuDropdown;
751
+ if (this._menuOutsideListener) {
752
+ document.removeEventListener("pointerdown", this._menuOutsideListener, true);
753
+ this._menuOutsideListener = null;
754
+ }
755
+ if (!menu) return;
756
+ menu.hidden = true;
757
+ for (const prop of ["position", "top", "bottom", "left", "right", "maxHeight", "overflowY"]) {
758
+ menu.style[prop] = "";
759
+ }
760
+ if (this._menuHome && menu.parentNode !== this._menuHome) this._menuHome.appendChild(menu);
761
+ }
719
762
  /**
720
763
  * Handle config button click — highlight tile and emit config request.
721
764
  * @private