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.
- package/CHANGELOG.md +43 -0
- package/LICENSE +0 -0
- package/README.md +0 -0
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/canvas.js +0 -0
- package/charts.js +0 -0
- package/core.js +0 -0
- package/css/base.css +21 -1
- package/css/flexdesk.css +30 -3
- package/css/overrides.css +0 -0
- package/css/reset.css +0 -0
- package/dist/canvas.js +0 -0
- package/dist/canvas.js.map +0 -0
- package/dist/charts.js +7 -6
- package/dist/charts.js.map +2 -2
- package/dist/{chunk-RMTRWWEO.js → chunk-2YTM3GJE.js} +67 -26
- package/dist/chunk-2YTM3GJE.js.map +7 -0
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/{chunk-UURZ37SD.js → chunk-7U4IB65H.js} +27 -9
- package/dist/chunk-7U4IB65H.js.map +7 -0
- package/dist/{chunk-D3COLZZ2.js → chunk-BE2PU3LL.js} +2 -2
- package/dist/{chunk-D3COLZZ2.js.map → chunk-BE2PU3LL.js.map} +0 -0
- package/dist/chunk-CVMCZZX2.js +0 -0
- package/dist/chunk-CVMCZZX2.js.map +0 -0
- package/dist/{chunk-UWO43BZU.js → chunk-F35XSQWA.js} +16 -20
- package/dist/chunk-F35XSQWA.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/chunk-GOQLR5WR.js +0 -0
- package/dist/chunk-GOQLR5WR.js.map +0 -0
- package/dist/chunk-HNU6MM2B.js +0 -0
- package/dist/chunk-HNU6MM2B.js.map +0 -0
- package/dist/chunk-JKZLNKG6.js +0 -0
- package/dist/chunk-JKZLNKG6.js.map +0 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-RKNFBX5K.js → chunk-MIW7D6WC.js} +16 -4
- package/dist/{chunk-RKNFBX5K.js.map → chunk-MIW7D6WC.js.map} +2 -2
- package/dist/chunk-N7HC3ADV.js +0 -0
- package/dist/chunk-N7HC3ADV.js.map +0 -0
- package/dist/{chunk-LWV23YVE.js → chunk-NIJEJAZ7.js} +42 -18
- package/dist/chunk-NIJEJAZ7.js.map +7 -0
- package/dist/{chunk-HYJYYEPK.js → chunk-REJ5HLIM.js} +2 -2
- package/dist/{chunk-HYJYYEPK.js.map → chunk-REJ5HLIM.js.map} +0 -0
- package/dist/{chunk-WVFGV5FT.js → chunk-WUFLGETN.js} +39 -2
- package/dist/{chunk-WVFGV5FT.js.map → chunk-WUFLGETN.js.map} +3 -3
- package/dist/core.js +0 -0
- package/dist/core.js.map +0 -0
- package/dist/editor.js +2 -2
- package/dist/editor.js.map +0 -0
- package/dist/flexdesk.css +30 -3
- package/dist/flow.js +3 -3
- package/dist/flow.js.map +0 -0
- package/dist/host.js +0 -0
- package/dist/host.js.map +0 -0
- package/dist/reset.css +0 -0
- package/dist/tile_registry-6WZPTUZV.js +0 -0
- package/dist/tile_registry-6WZPTUZV.js.map +0 -0
- package/dist/tiles.js +63 -20
- package/dist/tiles.js.map +2 -2
- package/dist/widgets.js +6 -6
- package/dist/widgets.js.map +0 -0
- package/dist/wm.js +5 -5
- package/dist/wm.js.map +0 -0
- package/docs/README.md +0 -0
- package/docs/best-practices.md +0 -0
- package/docs/components/charts.md +95 -0
- package/docs/components/command-palette.md +0 -0
- package/docs/components/conversation.md +0 -0
- package/docs/components/data-table-grid.md +0 -0
- package/docs/components/data-table.md +0 -0
- package/docs/components/editor.md +0 -0
- package/docs/components/flow-canvas-editor.md +0 -0
- package/docs/components/flow-editors.md +0 -0
- package/docs/components/flow-lane-editor.md +0 -0
- package/docs/components/flow-outline-editor.md +0 -0
- package/docs/components/forms-and-dialogs.md +0 -0
- package/docs/components/input-fields.md +0 -0
- package/docs/components/keymap.md +0 -0
- package/docs/components/markdown-and-outline.md +0 -0
- package/docs/components/menus.md +41 -0
- package/docs/components/notifications.md +0 -0
- package/docs/components/panels-and-desktops.md +0 -0
- package/docs/components/record-list-and-fact-table.md +0 -0
- package/docs/components/settings-view.md +0 -0
- package/docs/components/shell.md +0 -0
- package/docs/components/slide-out-panel.md +0 -0
- package/docs/components/tiles.md +25 -0
- package/docs/components/wizard.md +0 -0
- package/docs/concepts/building-and-packaging.md +0 -0
- package/docs/concepts/domain-neutrality.md +0 -0
- package/docs/concepts/host-port.md +0 -0
- package/docs/concepts/packages-and-entry-points.md +0 -0
- package/docs/concepts/peer-dependencies.md +0 -0
- package/docs/concepts/state-guard.md +0 -0
- package/docs/concepts/styling.md +0 -0
- package/docs/quickstart.md +0 -0
- package/editor.js +0 -0
- package/flow.js +0 -0
- package/host.js +0 -0
- package/package.json +1 -1
- package/src/canvas/canvas_adapter.js +0 -0
- package/src/canvas/edge_router.js +0 -0
- package/src/canvas/graph/connector_router.js +0 -0
- package/src/canvas/graph/history_service.js +0 -0
- package/src/canvas/graph/node_platform.js +0 -0
- package/src/canvas/index.js +0 -0
- package/src/canvas/layered_layout.js +0 -0
- package/src/charts/chart_types.js +3 -2
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +0 -0
- package/src/charts/plot_config.js +0 -0
- package/src/charts/plot_config_editor.js +8 -2
- package/src/charts/plot_grid.js +16 -4
- package/src/charts/plot_popout_window.js +2 -1
- package/src/charts/plotly_wrapper.js +0 -0
- package/src/charts/series_transforms.js +114 -20
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +0 -0
- package/src/core/settings.js +0 -0
- package/src/core/state_guard.js +0 -0
- package/src/core/state_guard_config.js +0 -0
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +0 -0
- package/src/editor/monaco_editor_factory.js +0 -0
- package/src/editor/monaco_loader.js +0 -0
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_tab_bar.js +26 -7
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +0 -0
- package/src/flow/canvas_editor/connections.js +0 -0
- package/src/flow/canvas_editor/editor.js +0 -0
- package/src/flow/canvas_editor/index.js +0 -0
- package/src/flow/canvas_editor/layout.js +0 -0
- package/src/flow/canvas_editor/strings.js +0 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +0 -0
- package/src/flow/kit/chip_input.js +0 -0
- package/src/flow/kit/dom.js +0 -0
- package/src/flow/kit/findings.js +0 -0
- package/src/flow/kit/history.js +0 -0
- package/src/flow/kit/index.js +0 -0
- package/src/flow/kit/keys.js +0 -0
- package/src/flow/kit/logic_graph.js +0 -0
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +0 -0
- package/src/flow/kit/step_picker.js +0 -0
- package/src/flow/kit/strings.js +0 -0
- package/src/flow/kit/value_picker.js +0 -0
- package/src/flow/kit/widgets.js +0 -0
- package/src/flow/lanes/editor.js +0 -0
- package/src/flow/lanes/index.js +0 -0
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +0 -0
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +0 -0
- package/src/flow/outline/editor.js +0 -0
- package/src/flow/outline/index.js +0 -0
- package/src/flow/outline/mapping.js +0 -0
- package/src/flow/outline/menu.js +0 -0
- package/src/flow/outline/operations.js +0 -0
- package/src/flow/outline/recognise.js +0 -0
- package/src/flow/outline/strings.js +0 -0
- package/src/help/help_modal.js +0 -0
- package/src/help/help_registry.js +0 -0
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +0 -0
- package/src/host/host.js +0 -0
- package/src/host/photino_host.js +0 -0
- package/src/host/photino_host.md +0 -0
- package/src/host/pywebview_host.js +0 -0
- package/src/tiles/chart_tiles.js +0 -0
- package/src/tiles/config_schema.js +0 -0
- package/src/tiles/figure_tile.js +0 -0
- package/src/tiles/layout_persistence.js +0 -0
- package/src/tiles/list_tiles.js +0 -0
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_base.js +67 -16
- package/src/tiles/tile_config_form.js +0 -0
- package/src/tiles/tile_grid.js +0 -0
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +0 -0
- package/src/tiling/command_palette.js +0 -0
- package/src/tiling/content_registry.js +0 -0
- package/src/tiling/desktops.js +0 -0
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +0 -0
- package/src/tiling/kind_taxonomy.js +0 -0
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +0 -0
- package/src/tiling/page_factory.js +0 -0
- package/src/tiling/panel_availability.js +0 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +0 -0
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_breadcrumb.js +0 -0
- package/src/tiling/tile_renderer.js +0 -0
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/tile_tree.js +0 -0
- package/src/tiling/wm.js +0 -0
- package/src/tiling/zoom.js +0 -0
- package/src/ui/base/component_base.js +0 -0
- package/src/ui/base/controller_base.js +0 -0
- package/src/ui/base/page_base.js +0 -0
- package/src/ui/components/about_dialog.js +0 -0
- package/src/ui/components/action_dropdown.js +19 -6
- package/src/ui/components/alert_log.js +0 -0
- package/src/ui/components/alerts_pane.js +0 -0
- package/src/ui/components/attribute_list_editor.js +0 -0
- package/src/ui/components/autocomplete_field.js +0 -0
- package/src/ui/components/cell_editor_host.js +0 -0
- package/src/ui/components/cell_editors.js +0 -0
- package/src/ui/components/cell_grid.js +0 -0
- package/src/ui/components/cell_paste.js +0 -0
- package/src/ui/components/cell_selection.js +0 -0
- package/src/ui/components/choice_controls.js +0 -0
- package/src/ui/components/color_field.js +0 -0
- package/src/ui/components/color_list_field.js +0 -0
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +0 -0
- package/src/ui/components/column_order.js +0 -0
- package/src/ui/components/computing_status_window.js +0 -0
- package/src/ui/components/confirm_dialog.js +0 -0
- package/src/ui/components/context_menu.js +20 -20
- package/src/ui/components/conversation_view.js +0 -0
- package/src/ui/components/crosstab.js +0 -0
- package/src/ui/components/data_table.js +45 -19
- package/src/ui/components/detail_header.js +0 -0
- package/src/ui/components/document_outline.js +0 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/fact_table.js +0 -0
- package/src/ui/components/fill_handle.js +0 -0
- package/src/ui/components/gallery_picker.js +0 -0
- package/src/ui/components/header_actions.js +0 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +0 -0
- package/src/ui/components/managed_window.js +0 -0
- package/src/ui/components/menu_bar.js +0 -0
- package/src/ui/components/modal.js +0 -0
- package/src/ui/components/notification_history.js +0 -0
- package/src/ui/components/progress.js +0 -0
- package/src/ui/components/record_list.js +0 -0
- package/src/ui/components/row_activation.js +0 -0
- package/src/ui/components/section_stack.js +0 -0
- package/src/ui/components/series.js +0 -0
- package/src/ui/components/settings_view.js +0 -0
- package/src/ui/components/slide_out_panel.js +0 -0
- package/src/ui/components/slider_field.js +0 -0
- package/src/ui/components/sortable_list.js +0 -0
- package/src/ui/components/staged_edits.js +0 -0
- package/src/ui/components/staged_history.js +0 -0
- package/src/ui/components/table_filter_chips.js +0 -0
- package/src/ui/components/table_state_store.js +0 -0
- package/src/ui/components/table_structure.js +0 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +0 -0
- package/src/ui/components/tag_input.js +0 -0
- package/src/ui/components/toast.js +0 -0
- package/src/ui/components/toggle_field.js +0 -0
- package/src/ui/components/tree_view.js +0 -0
- package/src/ui/components/tsv.js +0 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/wizard.js +0 -0
- package/src/ui/controllers/window_chrome_controller.js +0 -0
- package/src/ui/notification_center.js +0 -0
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/markdown.js +0 -0
- package/src/ui/utils/menu_viewport.js +81 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +0 -0
- package/src/ui/utils/raf_resize_observer.js +0 -0
- package/src/ui/utils/scroll_lock.js +0 -0
- package/src/ui/utils/strip_scroll.js +0 -0
- package/src/ui/utils/tooltip_service.js +0 -0
- package/tiles.js +0 -0
- package/widgets.js +0 -0
- package/wm.js +0 -0
- package/dist/chunk-LWV23YVE.js.map +0 -7
- package/dist/chunk-RMTRWWEO.js.map +0 -7
- package/dist/chunk-UURZ37SD.js.map +0 -7
- package/dist/chunk-UWO43BZU.js.map +0 -7
|
@@ -379,8 +379,12 @@ export class ActionDropdown {
|
|
|
379
379
|
*
|
|
380
380
|
* @param {HTMLElement} trigger - The element the dropdown opens from
|
|
381
381
|
* @param {HTMLElement} menu - The dropdown menu element to position
|
|
382
|
+
* @param {Object} [opts]
|
|
383
|
+
* @param {'start'|'end'} [opts.align='start'] - Which trigger edge the menu
|
|
384
|
+
* lines up with first: `'start'` its left edge, `'end'` its right edge.
|
|
385
|
+
* Either flips to the other edge when it would leave the window.
|
|
382
386
|
*/
|
|
383
|
-
static position(trigger, menu) {
|
|
387
|
+
static position(trigger, menu, { align = 'start' } = {}) {
|
|
384
388
|
if (!trigger || !menu) return;
|
|
385
389
|
|
|
386
390
|
// Reset any prior inline position so natural size can be measured.
|
|
@@ -424,12 +428,21 @@ export class ActionDropdown {
|
|
|
424
428
|
top = EDGE_MARGIN;
|
|
425
429
|
}
|
|
426
430
|
|
|
427
|
-
// ── Horizontal: align
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (
|
|
431
|
-
// Align right edges instead.
|
|
431
|
+
// ── Horizontal: align with the trigger's start (left) or end (right)
|
|
432
|
+
// edge, flip to the other edge if off-screen ──
|
|
433
|
+
let left;
|
|
434
|
+
if (align === 'end') {
|
|
432
435
|
left = triggerRect.right - menuRect.width;
|
|
436
|
+
if (left < EDGE_MARGIN) {
|
|
437
|
+
// Align left edges instead.
|
|
438
|
+
left = triggerRect.left;
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
left = triggerRect.left;
|
|
442
|
+
if (left + menuRect.width > vw - EDGE_MARGIN) {
|
|
443
|
+
// Align right edges instead.
|
|
444
|
+
left = triggerRect.right - menuRect.width;
|
|
445
|
+
}
|
|
433
446
|
}
|
|
434
447
|
|
|
435
448
|
// Still off-screen? Clamp to edges.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -24,9 +24,16 @@
|
|
|
24
24
|
* Disabled items are skipped by the arrows rather than focusable-but-inert.
|
|
25
25
|
* `<button>` rather than a div with `tabindex`: the browser then gives Enter and
|
|
26
26
|
* Space for free and screen readers announce it without further help.
|
|
27
|
+
*
|
|
28
|
+
* ── It stays inside the window (BUG-0040) ─────────────────────────────
|
|
29
|
+
* The top was clamped at 0 but nothing capped the height, so a long menu ran
|
|
30
|
+
* off the bottom and could not scroll. `placeMenuInViewport` caps it at the
|
|
31
|
+
* window's height less a margin; it scrolls inside, the arrows scroll the
|
|
32
|
+
* focused item into view, and scrolling the menu itself does not close it.
|
|
27
33
|
*/
|
|
28
34
|
|
|
29
35
|
import { modalHost } from './modal.js';
|
|
36
|
+
import { placeMenuInViewport, moveMenuFocus, focusMenuItem } from '../utils/menu_viewport.js';
|
|
30
37
|
|
|
31
38
|
let _activeMenu = null;
|
|
32
39
|
let _returnFocusTo = null;
|
|
@@ -92,23 +99,20 @@ export function showContextMenu(x, y, items, onAction) {
|
|
|
92
99
|
menu.style.display = 'block';
|
|
93
100
|
_activeMenu = menu;
|
|
94
101
|
|
|
95
|
-
// Position
|
|
96
|
-
|
|
97
|
-
const left = Math.min(x, window.innerWidth - rect.width - 8);
|
|
98
|
-
const top = Math.min(y, window.innerHeight - rect.height - 8);
|
|
99
|
-
menu.style.left = `${Math.max(0, left)}px`;
|
|
100
|
-
menu.style.top = `${Math.max(0, top)}px`;
|
|
102
|
+
// Position inside the window, scrolling when taller than it (BUG-0040).
|
|
103
|
+
placeMenuInViewport(menu, x, y);
|
|
101
104
|
|
|
102
|
-
// Close on outside click / scroll / Escape.
|
|
105
|
+
// Close on outside click / scroll / Escape. A scroll INSIDE the menu is
|
|
106
|
+
// the menu being read, not the page moving under it.
|
|
103
107
|
setTimeout(() => {
|
|
104
108
|
document.addEventListener('mousedown', _outsideHandler, { once: true, capture: true });
|
|
105
109
|
}, 0);
|
|
106
110
|
document.addEventListener('keydown', _keyHandler);
|
|
107
|
-
window.addEventListener('scroll',
|
|
111
|
+
window.addEventListener('scroll', _scrollHandler, { capture: true });
|
|
108
112
|
|
|
109
113
|
// Focus the first item the keyboard can actually use. `preventScroll` so a
|
|
110
114
|
// menu opened near the bottom of a long page does not jump it.
|
|
111
|
-
_enabledItems(menu)[0]
|
|
115
|
+
focusMenuItem(_enabledItems(menu)[0]);
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
export function hideContextMenu() {
|
|
@@ -119,6 +123,7 @@ export function hideContextMenu() {
|
|
|
119
123
|
_activeMenu = null;
|
|
120
124
|
_returnFocusTo = null;
|
|
121
125
|
document.removeEventListener('keydown', _keyHandler);
|
|
126
|
+
window.removeEventListener('scroll', _scrollHandler, { capture: true });
|
|
122
127
|
// Only take focus back if the menu still had it. A click elsewhere has
|
|
123
128
|
// already moved focus deliberately and must not be undone.
|
|
124
129
|
if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });
|
|
@@ -134,17 +139,12 @@ function _enabledItems(menu) {
|
|
|
134
139
|
function _keyHandler(e) {
|
|
135
140
|
if (!_activeMenu) return;
|
|
136
141
|
if (e.key === 'Escape') { e.preventDefault(); hideContextMenu(); return; }
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (e.
|
|
142
|
-
|
|
143
|
-
else if (e.key === 'Home') next = items[0];
|
|
144
|
-
else if (e.key === 'End') next = items[items.length - 1];
|
|
145
|
-
if (!next) return;
|
|
146
|
-
e.preventDefault();
|
|
147
|
-
next.focus({ preventScroll: true });
|
|
142
|
+
moveMenuFocus(e, _enabledItems(_activeMenu));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function _scrollHandler(e) {
|
|
146
|
+
if (_activeMenu && e.target?.nodeType && _activeMenu.contains(e.target)) return;
|
|
147
|
+
hideContextMenu();
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function _outsideHandler(e) {
|
|
File without changes
|
|
File without changes
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { createRafResizeObserver } from '../utils/raf_resize_observer.js';
|
|
23
|
+
import { placeMenuInViewport, moveMenuFocus, focusMenuItem } from '../utils/menu_viewport.js';
|
|
23
24
|
import {
|
|
24
25
|
ISO_DATE, ISO_DATETIME, formatDate, formatDuration, matchDateFilter,
|
|
25
26
|
matchDurationFilter, parseDateValue, parseDuration,
|
|
@@ -764,6 +765,8 @@ export class DataTable {
|
|
|
764
765
|
// STORY-0010. The right-click the custom items on show were built for,
|
|
765
766
|
// read again when one is chosen.
|
|
766
767
|
this._contextMenuCtx = null;
|
|
768
|
+
// BUG-0040. What had focus when the menu opened, given it back on close.
|
|
769
|
+
this._contextMenuReturnFocus = null;
|
|
767
770
|
// STORY-0010 `keyboardNav`: the row the arrows last moved to.
|
|
768
771
|
this._navCursor = null;
|
|
769
772
|
this._filterDropdownEl = null;
|
|
@@ -7281,6 +7284,7 @@ export class DataTable {
|
|
|
7281
7284
|
|
|
7282
7285
|
const menu = document.createElement('div');
|
|
7283
7286
|
menu.className = 'twm-context-menu data-context-menu';
|
|
7287
|
+
menu.setAttribute('role', 'menu');
|
|
7284
7288
|
menu.style.display = 'none';
|
|
7285
7289
|
menu.style.position = 'fixed';
|
|
7286
7290
|
menu.style.zIndex = '10001';
|
|
@@ -7338,19 +7342,31 @@ export class DataTable {
|
|
|
7338
7342
|
if (event?.target && menu?.contains(event.target)) return;
|
|
7339
7343
|
this._hideContextMenu();
|
|
7340
7344
|
};
|
|
7341
|
-
|
|
7342
|
-
|
|
7345
|
+
// BUG-0040. The keyboard reaches every item: Up/Down/Home/End move
|
|
7346
|
+
// focus (and scroll the item into view in a menu taller than the
|
|
7347
|
+
// window), Enter and Space choose, Escape closes.
|
|
7348
|
+
const onMenuKey = (event) => {
|
|
7349
|
+
if (event.key === 'Escape') { this._hideContextMenu(); return; }
|
|
7350
|
+
if (!menu || menu.style.display === 'none') return;
|
|
7351
|
+
const focused = document.activeElement;
|
|
7352
|
+
if ((event.key === 'Enter' || event.key === ' ')
|
|
7353
|
+
&& focused?.classList?.contains('twm-context-menu-item') && menu.contains(focused)) {
|
|
7354
|
+
event.preventDefault();
|
|
7355
|
+
focused.click();
|
|
7356
|
+
return;
|
|
7357
|
+
}
|
|
7358
|
+
moveMenuFocus(event, this._contextMenuEnabledItems());
|
|
7343
7359
|
};
|
|
7344
7360
|
const hideOnResize = () => this._hideContextMenu();
|
|
7345
7361
|
|
|
7346
7362
|
document.addEventListener('click', hideOnGlobal, true);
|
|
7347
7363
|
document.addEventListener('scroll', hideOnGlobal, true);
|
|
7348
7364
|
window.addEventListener('resize', hideOnResize);
|
|
7349
|
-
document.addEventListener('keydown',
|
|
7365
|
+
document.addEventListener('keydown', onMenuKey);
|
|
7350
7366
|
|
|
7351
7367
|
this._contextMenuGlobals.push(() => document.removeEventListener('click', hideOnGlobal, true));
|
|
7352
7368
|
this._contextMenuGlobals.push(() => document.removeEventListener('scroll', hideOnGlobal, true));
|
|
7353
|
-
this._contextMenuGlobals.push(() => document.removeEventListener('keydown',
|
|
7369
|
+
this._contextMenuGlobals.push(() => document.removeEventListener('keydown', onMenuKey));
|
|
7354
7370
|
this._contextMenuGlobals.push(() => window.removeEventListener('resize', hideOnResize));
|
|
7355
7371
|
}
|
|
7356
7372
|
|
|
@@ -7362,10 +7378,23 @@ export class DataTable {
|
|
|
7362
7378
|
}
|
|
7363
7379
|
|
|
7364
7380
|
_hideContextMenu() {
|
|
7365
|
-
|
|
7366
|
-
|
|
7381
|
+
const menu = this._contextMenuEl;
|
|
7382
|
+
const returnTo = this._contextMenuReturnFocus;
|
|
7383
|
+
this._contextMenuReturnFocus = null;
|
|
7384
|
+
const held = !!menu && menu.style.display !== 'none' && menu.contains(document.activeElement);
|
|
7385
|
+
if (menu) {
|
|
7386
|
+
menu.style.display = 'none';
|
|
7367
7387
|
}
|
|
7368
7388
|
this._disarmContextMenuGlobals();
|
|
7389
|
+
// BUG-0040. Focus goes back where it was, but only if the menu still
|
|
7390
|
+
// had it: a click elsewhere has moved it deliberately.
|
|
7391
|
+
if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });
|
|
7392
|
+
}
|
|
7393
|
+
|
|
7394
|
+
/** BUG-0040. The items the keyboard can land on. */
|
|
7395
|
+
_contextMenuEnabledItems() {
|
|
7396
|
+
const menu = this._contextMenuEl;
|
|
7397
|
+
return menu ? [...menu.querySelectorAll('.twm-context-menu-item:not(.disabled)')] : [];
|
|
7369
7398
|
}
|
|
7370
7399
|
|
|
7371
7400
|
_showContextMenu(clientX, clientY, custom = null) {
|
|
@@ -7381,20 +7410,17 @@ export class DataTable {
|
|
|
7381
7410
|
item.style.pointerEvents = hasSelection ? '' : 'none';
|
|
7382
7411
|
});
|
|
7383
7412
|
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
if (
|
|
7390
|
-
left = window.innerWidth - rect.width - 8;
|
|
7391
|
-
}
|
|
7392
|
-
if (top + rect.height > window.innerHeight) {
|
|
7393
|
-
top = window.innerHeight - rect.height - 8;
|
|
7394
|
-
}
|
|
7413
|
+
// BUG-0040. Every item is a menu item the keyboard can focus.
|
|
7414
|
+
menu.querySelectorAll('.twm-context-menu-item').forEach((item) => {
|
|
7415
|
+
item.tabIndex = -1;
|
|
7416
|
+
if (!item.hasAttribute('role')) item.setAttribute('role', 'menuitem');
|
|
7417
|
+
});
|
|
7418
|
+
if (!menu.contains(document.activeElement)) this._contextMenuReturnFocus = document.activeElement;
|
|
7395
7419
|
|
|
7396
|
-
|
|
7397
|
-
menu.style.
|
|
7420
|
+
// BUG-0040. Inside the window, at most its height, scrolling when longer.
|
|
7421
|
+
menu.style.display = 'block';
|
|
7422
|
+
placeMenuInViewport(menu, clientX, clientY);
|
|
7423
|
+
focusMenuItem(this._contextMenuEnabledItems()[0]);
|
|
7398
7424
|
}
|
|
7399
7425
|
|
|
7400
7426
|
/**
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/components/tsv.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/ui/utils/markdown.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* menu_viewport.js — keeping a context menu inside the window (BUG-0040).
|
|
3
|
+
*
|
|
4
|
+
* FlexDesk draws three context menus of its own: `showContextMenu`
|
|
5
|
+
* (../components/context_menu.js), the DataTable's right-click menu
|
|
6
|
+
* (../components/data_table.js) and `NotebookTabBar`'s tab menu
|
|
7
|
+
* (../../editor/notebook_tab_bar.js). Each placed itself with its own copy of
|
|
8
|
+
* `top = min(y, innerHeight - height - 8)`, and none had a max-height. A menu
|
|
9
|
+
* taller than the window then opened above its top edge (the DataTable's did
|
|
10
|
+
* not clamp at all: a 101-item row menu opened at y=-2153 in a 761 px window)
|
|
11
|
+
* or ran off its bottom, and nothing scrolled, so the items past the edge could
|
|
12
|
+
* not be reached by any means.
|
|
13
|
+
*
|
|
14
|
+
* One placement now, for all three:
|
|
15
|
+
* - the menu is at most the window's height less a margin above and below,
|
|
16
|
+
* and scrolls inside when it is longer (`overflow-y: auto` in flexdesk.css);
|
|
17
|
+
* - it opens at the pointer, moved left or up just enough to fit, and never
|
|
18
|
+
* above the window's top or left edge;
|
|
19
|
+
* - keyboard movement focuses an item and scrolls it into view within the
|
|
20
|
+
* menu, so every item can be reached with the arrows.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** The gap kept between a menu and the window's edges, in px. */
|
|
24
|
+
export const MENU_VIEWPORT_MARGIN = 8;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Size and place an open (displayed) menu at `(x, y)` inside its window.
|
|
28
|
+
*
|
|
29
|
+
* @param {HTMLElement} menu the menu, already in the document and displayed
|
|
30
|
+
* @param {number} x client x of the pointer
|
|
31
|
+
* @param {number} y client y of the pointer
|
|
32
|
+
* @param {number} [margin] gap to the window's edges
|
|
33
|
+
*/
|
|
34
|
+
export function placeMenuInViewport(menu, x, y, margin = MENU_VIEWPORT_MARGIN) {
|
|
35
|
+
const view = menu.ownerDocument?.defaultView || window;
|
|
36
|
+
const vw = view.innerWidth;
|
|
37
|
+
const vh = view.innerHeight;
|
|
38
|
+
const maxHeight = Math.max(0, vh - 2 * margin);
|
|
39
|
+
menu.style.maxHeight = `${maxHeight}px`;
|
|
40
|
+
menu.scrollTop = 0;
|
|
41
|
+
// flexdesk.css makes the menu `box-sizing: border-box`, so its measured
|
|
42
|
+
// height (padding and border included) is at most `maxHeight`.
|
|
43
|
+
const rect = menu.getBoundingClientRect();
|
|
44
|
+
const left = Math.min(x, vw - rect.width - margin);
|
|
45
|
+
const top = Math.min(y, vh - rect.height - margin);
|
|
46
|
+
menu.style.left = `${Math.max(0, left)}px`;
|
|
47
|
+
menu.style.top = `${Math.max(0, top)}px`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Focus a menu item and bring it into view inside the (scrolling) menu.
|
|
52
|
+
* `preventScroll` keeps the page where it is; `block: 'nearest'` scrolls the
|
|
53
|
+
* menu only as far as the item needs.
|
|
54
|
+
*/
|
|
55
|
+
export function focusMenuItem(item) {
|
|
56
|
+
if (!item) return;
|
|
57
|
+
item.focus({ preventScroll: true });
|
|
58
|
+
item.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Up/Down/Home/End over `items` (the enabled ones), wrapping, from the one
|
|
63
|
+
* that has focus. Returns true when the key moved focus (the caller's event
|
|
64
|
+
* has then been `preventDefault`ed).
|
|
65
|
+
*
|
|
66
|
+
* @param {KeyboardEvent} e
|
|
67
|
+
* @param {HTMLElement[]} items
|
|
68
|
+
*/
|
|
69
|
+
export function moveMenuFocus(e, items) {
|
|
70
|
+
if (!items.length) return false;
|
|
71
|
+
const at = items.indexOf(items[0].ownerDocument.activeElement);
|
|
72
|
+
let next = null;
|
|
73
|
+
if (e.key === 'ArrowDown') next = items[(at + 1 + items.length) % items.length];
|
|
74
|
+
else if (e.key === 'ArrowUp') next = items[(at - 1 + items.length) % items.length];
|
|
75
|
+
else if (e.key === 'Home') next = items[0];
|
|
76
|
+
else if (e.key === 'End') next = items[items.length - 1];
|
|
77
|
+
if (!next) return false;
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
focusMenuItem(next);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/tiles.js
CHANGED
|
File without changes
|
package/widgets.js
CHANGED
|
File without changes
|
package/wm.js
CHANGED
|
File without changes
|