flexdesk 0.17.17 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/LICENSE +0 -0
- package/README.md +1 -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/flexdesk.css +7499 -5851
- package/css/reset.css +1 -1
- package/css/tokens.css +23 -1
- package/dist/canvas.js +0 -0
- package/dist/canvas.js.map +0 -0
- package/dist/charts.js +10 -6
- package/dist/charts.js.map +1 -1
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-5W4EEKSK.js +778 -0
- package/dist/chunk-5W4EEKSK.js.map +7 -0
- package/dist/chunk-DNJ3N4T4.js +82 -0
- package/dist/chunk-DNJ3N4T4.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-JKZLNKG6.js → chunk-HKPB6OFA.js} +90 -25
- package/dist/{chunk-JKZLNKG6.js.map → chunk-HKPB6OFA.js.map} +2 -2
- package/dist/chunk-HNU6MM2B.js +0 -0
- package/dist/chunk-HNU6MM2B.js.map +0 -0
- package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
- package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
- package/dist/chunk-N7HC3ADV.js +0 -0
- package/dist/chunk-N7HC3ADV.js.map +0 -0
- package/dist/{chunk-7OL6F2AM.js → chunk-P74F6I6P.js} +6 -4
- package/dist/{chunk-7OL6F2AM.js.map → chunk-P74F6I6P.js.map} +1 -1
- package/dist/chunk-PAI2ZUWV.js +260 -0
- package/dist/chunk-PAI2ZUWV.js.map +7 -0
- package/dist/chunk-Q7BALEPX.js +527 -0
- package/dist/chunk-Q7BALEPX.js.map +7 -0
- package/dist/chunk-QXAOTGCC.js +40 -0
- package/dist/chunk-QXAOTGCC.js.map +7 -0
- package/dist/{chunk-XJNADRGR.js → chunk-RKVYIXDC.js} +664 -91
- package/dist/chunk-RKVYIXDC.js.map +7 -0
- package/dist/{chunk-2Z2ENHR3.js → chunk-U5GKJUOC.js} +3 -3
- package/dist/{chunk-2Z2ENHR3.js.map → chunk-U5GKJUOC.js.map} +1 -1
- package/dist/chunk-UKP22RUG.js +219 -0
- package/dist/chunk-UKP22RUG.js.map +7 -0
- package/dist/chunk-VLOAI3HS.js +43 -0
- package/dist/chunk-VLOAI3HS.js.map +7 -0
- package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
- package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
- package/dist/{chunk-PGZ4EHSE.js → chunk-ZL3VKINP.js} +41 -390
- package/dist/chunk-ZL3VKINP.js.map +7 -0
- package/dist/editor.js +3 -2
- package/dist/editor.js.map +1 -1
- package/dist/flexdesk.css +7499 -5851
- package/dist/flow.js +29 -97
- package/dist/flow.js.map +3 -3
- package/dist/git.js +1356 -0
- package/dist/git.js.map +7 -0
- package/dist/host.js +0 -0
- package/dist/host.js.map +0 -0
- package/dist/reset.css +1 -1
- package/dist/tile_registry-6WZPTUZV.js +0 -0
- package/dist/tile_registry-6WZPTUZV.js.map +0 -0
- package/dist/tiles.js +20 -15
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +23 -1
- package/dist/widgets.js +3969 -1189
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +121 -33
- package/dist/wm.js.map +2 -2
- package/docs/README.md +9 -4
- package/docs/best-practices.md +2 -2
- package/docs/components/chat-shell.md +198 -0
- package/docs/components/command-palette.md +0 -0
- package/docs/components/conversation.md +10 -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/filter-builder.md +246 -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 +22 -0
- package/docs/components/git.md +355 -0
- package/docs/components/input-fields.md +167 -2
- package/docs/components/keymap.md +8 -0
- package/docs/components/markdown-and-outline.md +0 -0
- package/docs/components/menus.md +120 -3
- package/docs/components/panels-and-desktops.md +16 -5
- package/docs/components/record-list-and-fact-table.md +0 -0
- package/docs/components/settings-view.md +0 -0
- package/docs/components/shell.md +104 -1
- package/docs/components/wizard.md +0 -0
- package/docs/components/workspace-picker.md +149 -0
- package/docs/concepts/building-and-packaging.md +2 -2
- package/docs/concepts/domain-neutrality.md +0 -0
- package/docs/concepts/host-port.md +0 -0
- package/docs/concepts/packages-and-entry-points.md +14 -13
- package/docs/concepts/peer-dependencies.md +0 -0
- package/docs/concepts/state-guard.md +3 -3
- package/docs/quickstart.md +1 -1
- package/editor.js +0 -0
- package/flow.js +0 -0
- package/git.js +13 -0
- package/host.js +0 -0
- package/package.json +6 -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/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/plotly_wrapper.js +0 -0
- 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_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_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 +28 -119
- 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 +1 -1
- package/src/flow/kit/strings.js +0 -0
- package/src/flow/kit/value_picker.js +1 -1
- 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 +1 -1
- 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/git/diff_viewer.js +212 -0
- package/src/git/diff_window.js +51 -0
- package/src/git/git_graph.js +401 -0
- package/src/git/git_ops.js +61 -0
- package/src/git/git_panel.js +755 -0
- package/src/git/index.js +9 -0
- package/src/git/line_diff.js +202 -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 +5 -4
- package/src/tiles/tile_config_form.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 +10 -8
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +6 -5
- package/src/tiling/kind_taxonomy.js +15 -2
- 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 +124 -17
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_renderer.js +2 -3
- package/src/tiling/tile_tab_menu.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 +4 -78
- package/src/ui/components/alert_log.js +0 -0
- package/src/ui/components/alerts_pane.js +0 -0
- package/src/ui/components/anchored_layer.js +255 -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/chat_shell.js +920 -0
- package/src/ui/components/chat_state_machine.js +223 -0
- package/src/ui/components/choice_controls.js +45 -2
- package/src/ui/components/color_field.js +3 -2
- package/src/ui/components/color_list_field.js +3 -2
- 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/combobox.js +422 -0
- package/src/ui/components/conversation_view.js +17 -2
- package/src/ui/components/crosstab.js +0 -0
- package/src/ui/components/data_table.js +2 -2
- 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/filter_builder.js +766 -0
- package/src/ui/components/filter_model.js +614 -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 +24 -47
- package/src/ui/components/menu_bar.js +101 -24
- package/src/ui/components/modal.js +79 -109
- package/src/ui/components/option_list.js +160 -0
- package/src/ui/components/popover.js +94 -0
- package/src/ui/components/progress.js +0 -0
- package/src/ui/components/recent_items.js +169 -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/select.js +322 -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 +77 -8
- package/src/ui/components/toast.js +0 -0
- package/src/ui/components/toggle_field.js +0 -0
- package/src/ui/components/tsv.js +0 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/workspace_picker.js +1155 -0
- package/src/ui/controllers/window_chrome_controller.js +4 -1
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/fire_event.js +17 -0
- package/src/ui/utils/focusable.js +25 -0
- package/src/ui/utils/markdown.js +0 -0
- package/src/ui/utils/modal_host.js +27 -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 +12 -2
- package/wm.js +0 -0
- package/css/base.css +0 -10527
- package/css/overrides.css +0 -2596
- package/dist/chunk-7JI7HHSY.js +0 -107
- package/dist/chunk-7JI7HHSY.js.map +0 -7
- package/dist/chunk-MIW7D6WC.js +0 -526
- package/dist/chunk-MIW7D6WC.js.map +0 -7
- package/dist/chunk-PGZ4EHSE.js.map +0 -7
- package/dist/chunk-XJNADRGR.js.map +0 -7
- /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
package/dist/chunk-7JI7HHSY.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
modalHost
|
|
3
|
-
} from "./chunk-XJNADRGR.js";
|
|
4
|
-
import {
|
|
5
|
-
focusMenuItem,
|
|
6
|
-
moveMenuFocus,
|
|
7
|
-
placeMenuInViewport
|
|
8
|
-
} from "./chunk-WUFLGETN.js";
|
|
9
|
-
|
|
10
|
-
// src/ui/components/context_menu.js
|
|
11
|
-
var _activeMenu = null;
|
|
12
|
-
var _returnFocusTo = null;
|
|
13
|
-
function showContextMenu(x, y, items, onAction) {
|
|
14
|
-
hideContextMenu();
|
|
15
|
-
_returnFocusTo = document.activeElement;
|
|
16
|
-
const menu = document.createElement("div");
|
|
17
|
-
menu.className = "twm-context-menu ea-context-menu";
|
|
18
|
-
menu.setAttribute("role", "menu");
|
|
19
|
-
for (const it of items) {
|
|
20
|
-
if (it.separator) {
|
|
21
|
-
const sep = document.createElement("div");
|
|
22
|
-
sep.className = "twm-context-menu__separator";
|
|
23
|
-
sep.setAttribute("role", "separator");
|
|
24
|
-
menu.appendChild(sep);
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
const row = document.createElement("button");
|
|
28
|
-
row.type = "button";
|
|
29
|
-
row.setAttribute("role", "menuitem");
|
|
30
|
-
let cls = "twm-context-menu-item";
|
|
31
|
-
if (it.danger) cls += " twm-delete-node";
|
|
32
|
-
if (it.disabled) cls += " disabled";
|
|
33
|
-
row.className = cls;
|
|
34
|
-
if (it.disabled) {
|
|
35
|
-
row.disabled = true;
|
|
36
|
-
row.setAttribute("aria-disabled", "true");
|
|
37
|
-
}
|
|
38
|
-
if (it.title) row.title = it.title;
|
|
39
|
-
row.innerHTML = `
|
|
40
|
-
<span class="material-symbols-outlined">${it.icon || ""}</span>
|
|
41
|
-
<span>${escapeHtml(it.label)}</span>
|
|
42
|
-
`;
|
|
43
|
-
if (!it.disabled) {
|
|
44
|
-
row.addEventListener("click", (e) => {
|
|
45
|
-
e.stopPropagation();
|
|
46
|
-
hideContextMenu();
|
|
47
|
-
onAction?.(it.action);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
menu.appendChild(row);
|
|
51
|
-
}
|
|
52
|
-
(modalHost() || document.body).appendChild(menu);
|
|
53
|
-
menu.style.display = "block";
|
|
54
|
-
_activeMenu = menu;
|
|
55
|
-
placeMenuInViewport(menu, x, y);
|
|
56
|
-
setTimeout(() => {
|
|
57
|
-
document.addEventListener("mousedown", _outsideHandler, { once: true, capture: true });
|
|
58
|
-
}, 0);
|
|
59
|
-
document.addEventListener("keydown", _keyHandler);
|
|
60
|
-
window.addEventListener("scroll", _scrollHandler, { capture: true });
|
|
61
|
-
focusMenuItem(_enabledItems(menu)[0]);
|
|
62
|
-
}
|
|
63
|
-
function hideContextMenu() {
|
|
64
|
-
if (!_activeMenu) return;
|
|
65
|
-
const returnTo = _returnFocusTo;
|
|
66
|
-
const held = _activeMenu.contains(document.activeElement);
|
|
67
|
-
_activeMenu.remove();
|
|
68
|
-
_activeMenu = null;
|
|
69
|
-
_returnFocusTo = null;
|
|
70
|
-
document.removeEventListener("keydown", _keyHandler);
|
|
71
|
-
window.removeEventListener("scroll", _scrollHandler, { capture: true });
|
|
72
|
-
if (held && returnTo?.isConnected) returnTo.focus?.({ preventScroll: true });
|
|
73
|
-
}
|
|
74
|
-
function _enabledItems(menu) {
|
|
75
|
-
return [...menu.querySelectorAll(".twm-context-menu-item:not(.disabled)")];
|
|
76
|
-
}
|
|
77
|
-
function _keyHandler(e) {
|
|
78
|
-
if (!_activeMenu) return;
|
|
79
|
-
if (e.key === "Escape") {
|
|
80
|
-
e.preventDefault();
|
|
81
|
-
hideContextMenu();
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
moveMenuFocus(e, _enabledItems(_activeMenu));
|
|
85
|
-
}
|
|
86
|
-
function _scrollHandler(e) {
|
|
87
|
-
if (_activeMenu && e.target?.nodeType && _activeMenu.contains(e.target)) return;
|
|
88
|
-
hideContextMenu();
|
|
89
|
-
}
|
|
90
|
-
function _outsideHandler(e) {
|
|
91
|
-
if (_activeMenu && !_activeMenu.contains(e.target)) hideContextMenu();
|
|
92
|
-
}
|
|
93
|
-
function escapeHtml(s) {
|
|
94
|
-
return String(s).replace(/[&<>"']/g, (c) => ({
|
|
95
|
-
"&": "&",
|
|
96
|
-
"<": "<",
|
|
97
|
-
">": ">",
|
|
98
|
-
'"': """,
|
|
99
|
-
"'": "'"
|
|
100
|
-
})[c]);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export {
|
|
104
|
-
showContextMenu,
|
|
105
|
-
hideContextMenu
|
|
106
|
-
};
|
|
107
|
-
//# sourceMappingURL=chunk-7JI7HHSY.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
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 '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''',\n }[c]));\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
package/dist/chunk-MIW7D6WC.js
DELETED
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
// src/ui/base/component_base.js
|
|
2
|
-
var ComponentBase = class _ComponentBase {
|
|
3
|
-
constructor({ eventBus, logger } = {}) {
|
|
4
|
-
if (new.target === _ComponentBase) {
|
|
5
|
-
throw new Error("ComponentBase is abstract \u2013 extend it instead");
|
|
6
|
-
}
|
|
7
|
-
this.eventBus = eventBus;
|
|
8
|
-
this.logger = logger;
|
|
9
|
-
this._mounted = false;
|
|
10
|
-
}
|
|
11
|
-
mount() {
|
|
12
|
-
throw new Error("ComponentBase.mount must be implemented by subclasses");
|
|
13
|
-
}
|
|
14
|
-
update() {
|
|
15
|
-
}
|
|
16
|
-
dispose() {
|
|
17
|
-
this._mounted = false;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// src/ui/components/action_dropdown.js
|
|
22
|
-
var EDGE_MARGIN = 8;
|
|
23
|
-
var GAP = 8;
|
|
24
|
-
var ActionDropdown = class _ActionDropdown {
|
|
25
|
-
/**
|
|
26
|
-
* @param {Object} config
|
|
27
|
-
* @param {HTMLElement} config.trigger - Button element that triggers the dropdown
|
|
28
|
-
* @param {Array} config.options - Array of option objects with { type, label, icon, description? },
|
|
29
|
-
* or `{ separator: true }` for a divider between them
|
|
30
|
-
* @param {Function} config.onSelect - Callback when an option is selected
|
|
31
|
-
* @param {string} [config.className=''] - Additional CSS class for the dropdown
|
|
32
|
-
* @param {string} [config.menuId] - Optional ID for the menu element
|
|
33
|
-
*/
|
|
34
|
-
constructor(config) {
|
|
35
|
-
this.trigger = config.trigger;
|
|
36
|
-
this.options = config.options || [];
|
|
37
|
-
this.onSelect = config.onSelect;
|
|
38
|
-
this.className = config.className || "";
|
|
39
|
-
this.menuId = config.menuId;
|
|
40
|
-
this.isOpen = false;
|
|
41
|
-
this.menuEl = null;
|
|
42
|
-
this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);
|
|
43
|
-
this._boundHandleKeydown = this._handleKeydown.bind(this);
|
|
44
|
-
this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);
|
|
45
|
-
this._init();
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Initialize the dropdown.
|
|
49
|
-
* @private
|
|
50
|
-
*/
|
|
51
|
-
_init() {
|
|
52
|
-
if (!this.trigger) {
|
|
53
|
-
console.warn("[ActionDropdown] No trigger element provided");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
this._createMenu();
|
|
57
|
-
this.trigger.addEventListener("click", this._boundHandleTriggerClick);
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Create the dropdown menu element.
|
|
61
|
-
* @private
|
|
62
|
-
*/
|
|
63
|
-
_createMenu() {
|
|
64
|
-
const menu = document.createElement("div");
|
|
65
|
-
menu.className = `twm-action-dropdown-menu ${this.className}`.trim();
|
|
66
|
-
menu.setAttribute("role", "menu");
|
|
67
|
-
menu.hidden = true;
|
|
68
|
-
if (this.menuId) {
|
|
69
|
-
menu.id = this.menuId;
|
|
70
|
-
}
|
|
71
|
-
this._renderOptions(menu);
|
|
72
|
-
document.body.appendChild(menu);
|
|
73
|
-
this.menuEl = menu;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Render option buttons, and dividers for separator entries, into a
|
|
77
|
-
* container element. `data-index` stays the entry's index in `options`.
|
|
78
|
-
* @param {HTMLElement} container
|
|
79
|
-
* @private
|
|
80
|
-
*/
|
|
81
|
-
_renderOptions(container) {
|
|
82
|
-
container.innerHTML = "";
|
|
83
|
-
let pendingSeparator = false;
|
|
84
|
-
this.options.forEach((opt, index) => {
|
|
85
|
-
if (opt?.separator) {
|
|
86
|
-
pendingSeparator = container.childElementCount > 0;
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (pendingSeparator) {
|
|
90
|
-
const sep = document.createElement("div");
|
|
91
|
-
sep.className = "twm-action-dropdown-separator";
|
|
92
|
-
sep.setAttribute("role", "separator");
|
|
93
|
-
container.appendChild(sep);
|
|
94
|
-
pendingSeparator = false;
|
|
95
|
-
}
|
|
96
|
-
const optionBtn = document.createElement("button");
|
|
97
|
-
optionBtn.type = "button";
|
|
98
|
-
optionBtn.className = "twm-action-dropdown-option";
|
|
99
|
-
optionBtn.dataset.index = index;
|
|
100
|
-
optionBtn.dataset.type = opt.type;
|
|
101
|
-
optionBtn.setAttribute("role", "menuitem");
|
|
102
|
-
const iconHtml = opt.icon ? `<span class="material-symbols-outlined twm-option-icon">${opt.icon}</span>` : "";
|
|
103
|
-
const descHtml = opt.description ? `<span class="twm-option-desc">${opt.description}</span>` : "";
|
|
104
|
-
optionBtn.innerHTML = `
|
|
105
|
-
${iconHtml}
|
|
106
|
-
<span class="option-text">
|
|
107
|
-
<span class="twm-option-label">${opt.label}</span>
|
|
108
|
-
${descHtml}
|
|
109
|
-
</span>
|
|
110
|
-
`;
|
|
111
|
-
optionBtn.addEventListener("click", (e) => {
|
|
112
|
-
e.preventDefault();
|
|
113
|
-
e.stopPropagation();
|
|
114
|
-
this._selectOption(opt);
|
|
115
|
-
});
|
|
116
|
-
container.appendChild(optionBtn);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Handle trigger button click.
|
|
121
|
-
* @param {MouseEvent} e
|
|
122
|
-
* @private
|
|
123
|
-
*/
|
|
124
|
-
_handleTriggerClick(e) {
|
|
125
|
-
e.preventDefault();
|
|
126
|
-
e.stopPropagation();
|
|
127
|
-
this.toggle();
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Toggle the dropdown open/closed.
|
|
131
|
-
* @param {boolean} [forceState] - Optional forced state
|
|
132
|
-
*/
|
|
133
|
-
toggle(forceState) {
|
|
134
|
-
const shouldOpen = typeof forceState === "boolean" ? forceState : !this.isOpen;
|
|
135
|
-
if (shouldOpen) {
|
|
136
|
-
this.open();
|
|
137
|
-
} else {
|
|
138
|
-
this.close();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Open the dropdown with auto-positioning.
|
|
143
|
-
*/
|
|
144
|
-
open() {
|
|
145
|
-
if (this.isOpen || !this.menuEl) return;
|
|
146
|
-
this.isOpen = true;
|
|
147
|
-
this.menuEl.hidden = false;
|
|
148
|
-
_ActionDropdown.position(this.trigger, this.menuEl);
|
|
149
|
-
requestAnimationFrame(() => {
|
|
150
|
-
this.menuEl?.classList.add("visible");
|
|
151
|
-
});
|
|
152
|
-
this.trigger?.classList.add("twm-is-open");
|
|
153
|
-
if (this.trigger?.hasAttribute("aria-expanded")) {
|
|
154
|
-
this.trigger.setAttribute("aria-expanded", "true");
|
|
155
|
-
}
|
|
156
|
-
document.addEventListener("click", this._boundHandleDocumentClick, true);
|
|
157
|
-
document.addEventListener("keydown", this._boundHandleKeydown);
|
|
158
|
-
const firstOption = this.menuEl.querySelector(".twm-action-dropdown-option");
|
|
159
|
-
firstOption?.focus();
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Close the dropdown.
|
|
163
|
-
*/
|
|
164
|
-
close() {
|
|
165
|
-
if (!this.isOpen) return;
|
|
166
|
-
this.isOpen = false;
|
|
167
|
-
if (this.menuEl) {
|
|
168
|
-
this.menuEl.classList.remove("visible");
|
|
169
|
-
this.menuEl.hidden = true;
|
|
170
|
-
}
|
|
171
|
-
this.trigger?.classList.remove("twm-is-open");
|
|
172
|
-
if (this.trigger?.hasAttribute("aria-expanded")) {
|
|
173
|
-
this.trigger.setAttribute("aria-expanded", "false");
|
|
174
|
-
}
|
|
175
|
-
document.removeEventListener("click", this._boundHandleDocumentClick, true);
|
|
176
|
-
document.removeEventListener("keydown", this._boundHandleKeydown);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Handle document click (close on outside click).
|
|
180
|
-
* @param {MouseEvent} e
|
|
181
|
-
* @private
|
|
182
|
-
*/
|
|
183
|
-
_handleDocumentClick(e) {
|
|
184
|
-
if (!this.isOpen) return;
|
|
185
|
-
if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
this.close();
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Handle keyboard events.
|
|
192
|
-
* @param {KeyboardEvent} e
|
|
193
|
-
* @private
|
|
194
|
-
*/
|
|
195
|
-
_handleKeydown(e) {
|
|
196
|
-
if (e.key === "Escape") {
|
|
197
|
-
e.preventDefault();
|
|
198
|
-
e.stopPropagation();
|
|
199
|
-
this.close();
|
|
200
|
-
this.trigger?.focus();
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
204
|
-
e.preventDefault();
|
|
205
|
-
this._navigateOptions(e.key === "ArrowDown" ? 1 : -1);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Navigate options with arrow keys.
|
|
210
|
-
* @param {number} direction - 1 for down, -1 for up
|
|
211
|
-
* @private
|
|
212
|
-
*/
|
|
213
|
-
_navigateOptions(direction) {
|
|
214
|
-
const options = this.menuEl?.querySelectorAll(".twm-action-dropdown-option");
|
|
215
|
-
if (!options || options.length === 0) return;
|
|
216
|
-
const currentIndex = Array.from(options).findIndex((opt) => opt === document.activeElement);
|
|
217
|
-
let nextIndex = currentIndex + direction;
|
|
218
|
-
if (nextIndex < 0) nextIndex = options.length - 1;
|
|
219
|
-
if (nextIndex >= options.length) nextIndex = 0;
|
|
220
|
-
options[nextIndex]?.focus();
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Handle option selection.
|
|
224
|
-
* @param {Object} option - The selected option
|
|
225
|
-
* @private
|
|
226
|
-
*/
|
|
227
|
-
_selectOption(option) {
|
|
228
|
-
this.close();
|
|
229
|
-
if (this.onSelect) {
|
|
230
|
-
this.onSelect(option);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Update the options dynamically.
|
|
235
|
-
* @param {Array} newOptions - New options array
|
|
236
|
-
*/
|
|
237
|
-
setOptions(newOptions) {
|
|
238
|
-
this.options = newOptions;
|
|
239
|
-
if (this.menuEl) {
|
|
240
|
-
this._renderOptions(this.menuEl);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Destroy the dropdown and clean up.
|
|
245
|
-
*/
|
|
246
|
-
destroy() {
|
|
247
|
-
this.close();
|
|
248
|
-
this.trigger?.removeEventListener("click", this._boundHandleTriggerClick);
|
|
249
|
-
this.menuEl?.remove();
|
|
250
|
-
this.menuEl = null;
|
|
251
|
-
this.trigger = null;
|
|
252
|
-
this.options = [];
|
|
253
|
-
this.onSelect = null;
|
|
254
|
-
}
|
|
255
|
-
// =========================================================================
|
|
256
|
-
// STATIC — shared auto-positioning for any trigger + menu pair
|
|
257
|
-
// =========================================================================
|
|
258
|
-
/**
|
|
259
|
-
* Position a dropdown menu relative to a trigger element.
|
|
260
|
-
* Uses `position: fixed` to avoid overflow clipping.
|
|
261
|
-
* Picks above/below based on available space, aligns left/right edge,
|
|
262
|
-
* and applies `max-height` + `overflow-y: auto` when the menu is taller
|
|
263
|
-
* than the available space.
|
|
264
|
-
*
|
|
265
|
-
* @param {HTMLElement} trigger - The element the dropdown opens from
|
|
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.
|
|
271
|
-
*/
|
|
272
|
-
static position(trigger, menu, { align = "start" } = {}) {
|
|
273
|
-
if (!trigger || !menu) return;
|
|
274
|
-
Object.assign(menu.style, {
|
|
275
|
-
position: "fixed",
|
|
276
|
-
top: "auto",
|
|
277
|
-
bottom: "auto",
|
|
278
|
-
left: "auto",
|
|
279
|
-
right: "auto",
|
|
280
|
-
maxHeight: "",
|
|
281
|
-
overflowY: ""
|
|
282
|
-
});
|
|
283
|
-
const triggerRect = trigger.getBoundingClientRect();
|
|
284
|
-
const menuRect = menu.getBoundingClientRect();
|
|
285
|
-
const vw = window.innerWidth;
|
|
286
|
-
const vh = window.innerHeight;
|
|
287
|
-
const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;
|
|
288
|
-
const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;
|
|
289
|
-
let top;
|
|
290
|
-
let maxHeight;
|
|
291
|
-
if (menuRect.height <= spaceBelow) {
|
|
292
|
-
top = triggerRect.bottom + GAP;
|
|
293
|
-
maxHeight = spaceBelow;
|
|
294
|
-
} else if (menuRect.height <= spaceAbove) {
|
|
295
|
-
top = triggerRect.top - GAP - menuRect.height;
|
|
296
|
-
maxHeight = spaceAbove;
|
|
297
|
-
} else if (spaceBelow >= spaceAbove) {
|
|
298
|
-
top = triggerRect.bottom + GAP;
|
|
299
|
-
maxHeight = spaceBelow;
|
|
300
|
-
} else {
|
|
301
|
-
maxHeight = spaceAbove;
|
|
302
|
-
top = EDGE_MARGIN;
|
|
303
|
-
}
|
|
304
|
-
let left;
|
|
305
|
-
if (align === "end") {
|
|
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
|
-
}
|
|
315
|
-
}
|
|
316
|
-
left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));
|
|
317
|
-
Object.assign(menu.style, {
|
|
318
|
-
position: "fixed",
|
|
319
|
-
top: `${Math.round(top)}px`,
|
|
320
|
-
left: `${Math.round(left)}px`,
|
|
321
|
-
maxHeight: `${Math.round(maxHeight)}px`,
|
|
322
|
-
overflowY: "auto"
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
// src/ui/components/slide_out_panel.js
|
|
328
|
-
var SlideOutPanel = class extends ComponentBase {
|
|
329
|
-
/** @type {HTMLElement} */
|
|
330
|
-
#container = null;
|
|
331
|
-
/** @type {HTMLElement} */
|
|
332
|
-
#backdrop = null;
|
|
333
|
-
/** @type {HTMLElement} */
|
|
334
|
-
#panel = null;
|
|
335
|
-
/** @type {HTMLElement} */
|
|
336
|
-
#headerIcon = null;
|
|
337
|
-
/** @type {HTMLElement} */
|
|
338
|
-
#headerTitle = null;
|
|
339
|
-
/** @type {HTMLElement} */
|
|
340
|
-
#body = null;
|
|
341
|
-
/** @type {number} */
|
|
342
|
-
#width;
|
|
343
|
-
/** @type {Function|null} */
|
|
344
|
-
#onClose;
|
|
345
|
-
/** @type {boolean} */
|
|
346
|
-
#open = false;
|
|
347
|
-
/** @type {ResizeObserver|null} */
|
|
348
|
-
#hostObserver = null;
|
|
349
|
-
/**
|
|
350
|
-
* @param {object} opts
|
|
351
|
-
* @param {number} [opts.width=380] Panel width in px.
|
|
352
|
-
* @param {Function} [opts.onClose] Called after the panel closes.
|
|
353
|
-
*/
|
|
354
|
-
constructor({ width = 700, onClose } = {}) {
|
|
355
|
-
super();
|
|
356
|
-
this.#width = width;
|
|
357
|
-
this.#onClose = onClose ?? null;
|
|
358
|
-
}
|
|
359
|
-
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
360
|
-
/**
|
|
361
|
-
* Mount the panel DOM into a host container.
|
|
362
|
-
* @param {HTMLElement} container Must have position:relative; overflow:hidden.
|
|
363
|
-
*/
|
|
364
|
-
mount(container) {
|
|
365
|
-
if (this._mounted) return;
|
|
366
|
-
this.#container = container;
|
|
367
|
-
this.#backdrop = document.createElement("div");
|
|
368
|
-
this.#backdrop.className = "twm-slide-out-panel__backdrop";
|
|
369
|
-
this.#backdrop.addEventListener("click", this.#handleBackdropClick);
|
|
370
|
-
this.#panel = document.createElement("div");
|
|
371
|
-
this.#panel.className = "twm-slide-out-panel";
|
|
372
|
-
this.#panel.style.width = `${this.#width}px`;
|
|
373
|
-
const header = document.createElement("div");
|
|
374
|
-
header.className = "twm-slide-out-panel__header";
|
|
375
|
-
this.#headerIcon = document.createElement("span");
|
|
376
|
-
this.#headerIcon.className = "twm-slide-out-panel__icon material-symbols-outlined";
|
|
377
|
-
this.#headerTitle = document.createElement("span");
|
|
378
|
-
this.#headerTitle.className = "twm-slide-out-panel__title";
|
|
379
|
-
const closeBtn = document.createElement("button");
|
|
380
|
-
closeBtn.className = "twm-slide-out-panel__close twm-btn-icon";
|
|
381
|
-
closeBtn.type = "button";
|
|
382
|
-
closeBtn.title = "Close";
|
|
383
|
-
closeBtn.innerHTML = '<span class="material-symbols-outlined">close</span>';
|
|
384
|
-
closeBtn.addEventListener("click", this.#handleCloseClick);
|
|
385
|
-
header.append(this.#headerIcon, this.#headerTitle, closeBtn);
|
|
386
|
-
this.#body = document.createElement("div");
|
|
387
|
-
this.#body.className = "twm-slide-out-panel__body";
|
|
388
|
-
this.#panel.append(header, this.#body);
|
|
389
|
-
this.#container.append(this.#backdrop, this.#panel);
|
|
390
|
-
this._mounted = true;
|
|
391
|
-
}
|
|
392
|
-
dispose() {
|
|
393
|
-
this.close();
|
|
394
|
-
this.#backdrop?.remove();
|
|
395
|
-
this.#panel?.remove();
|
|
396
|
-
this.#backdrop = null;
|
|
397
|
-
this.#panel = null;
|
|
398
|
-
this.#body = null;
|
|
399
|
-
this.#container = null;
|
|
400
|
-
super.dispose();
|
|
401
|
-
}
|
|
402
|
-
// ─── Public API ─────────────────────────────────────────────────────────
|
|
403
|
-
/** The inner content element — callers render config into this. */
|
|
404
|
-
get contentEl() {
|
|
405
|
-
return this.#body;
|
|
406
|
-
}
|
|
407
|
-
get isOpen() {
|
|
408
|
-
return this.#open;
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Open (or re-open) the panel with a new title/icon.
|
|
412
|
-
* Clears previous content. Returns contentEl for rendering.
|
|
413
|
-
* @param {string} title
|
|
414
|
-
* @param {string} icon Material Symbols icon name.
|
|
415
|
-
* @returns {HTMLElement} contentEl
|
|
416
|
-
*/
|
|
417
|
-
open(title, icon) {
|
|
418
|
-
if (!this._mounted) return null;
|
|
419
|
-
this.#body.innerHTML = "";
|
|
420
|
-
this.#headerIcon.textContent = icon || "";
|
|
421
|
-
this.#headerIcon.style.display = icon ? "" : "none";
|
|
422
|
-
this.#headerTitle.textContent = title || "";
|
|
423
|
-
if (!this.#open) {
|
|
424
|
-
this.#open = true;
|
|
425
|
-
this.#backdrop.classList.add("twm-slide-out-panel__backdrop--visible");
|
|
426
|
-
this.#panel.classList.add("twm-slide-out-panel--open");
|
|
427
|
-
document.addEventListener("keydown", this.#handleEsc);
|
|
428
|
-
this.#trackGeometry();
|
|
429
|
-
window.addEventListener("scroll", this.#trackGeometry, true);
|
|
430
|
-
window.addEventListener("resize", this.#trackGeometry);
|
|
431
|
-
this.#observeHost();
|
|
432
|
-
}
|
|
433
|
-
return this.#body;
|
|
434
|
-
}
|
|
435
|
-
/** Close the panel and clear content. */
|
|
436
|
-
close() {
|
|
437
|
-
if (!this.#open) return;
|
|
438
|
-
this.#open = false;
|
|
439
|
-
this.#panel.classList.remove("twm-slide-out-panel--open");
|
|
440
|
-
this.#backdrop.classList.remove("twm-slide-out-panel__backdrop--visible");
|
|
441
|
-
document.removeEventListener("keydown", this.#handleEsc);
|
|
442
|
-
window.removeEventListener("scroll", this.#trackGeometry, true);
|
|
443
|
-
window.removeEventListener("resize", this.#trackGeometry);
|
|
444
|
-
this.#hostObserver?.disconnect();
|
|
445
|
-
this.#hostObserver = null;
|
|
446
|
-
this.#body.innerHTML = "";
|
|
447
|
-
this.#onClose?.();
|
|
448
|
-
}
|
|
449
|
-
// ─── Event handlers (arrow fns for stable `this`) ───────────────────────
|
|
450
|
-
#handleBackdropClick = () => {
|
|
451
|
-
this.close();
|
|
452
|
-
};
|
|
453
|
-
#handleCloseClick = () => {
|
|
454
|
-
this.close();
|
|
455
|
-
};
|
|
456
|
-
#handleEsc = (e) => {
|
|
457
|
-
if (e.key === "Escape") {
|
|
458
|
-
e.stopPropagation();
|
|
459
|
-
this.close();
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
/** The host moves or resizes without any window event when a tile
|
|
463
|
-
* splitter is dragged or a dock panel toggles, and the fixed geometry
|
|
464
|
-
* would then stay at the old coordinates. Observe the host AND each
|
|
465
|
-
* ancestor: a host can move without changing size of its own (a dock
|
|
466
|
-
* panel toggling beside a fixed-width tile shrinks the content area,
|
|
467
|
-
* an ancestor, and the tile only shifts). The callback runs
|
|
468
|
-
* synchronously, after layout and before paint, so the panel follows
|
|
469
|
-
* a drag without a frame of lag; it only writes the panel's own
|
|
470
|
-
* `position: fixed` geometry, which is out of flow and resizes
|
|
471
|
-
* nothing observed, so it cannot loop. */
|
|
472
|
-
#observeHost() {
|
|
473
|
-
this.#hostObserver = new ResizeObserver(this.#trackGeometry);
|
|
474
|
-
for (let el = this.#container; el; el = el.parentElement) {
|
|
475
|
-
this.#hostObserver.observe(el);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
/** Pin the panel to the right edge of its host's *visible* frame
|
|
479
|
-
* using `position: fixed`. Re-run on every window scroll/resize and
|
|
480
|
-
* every host resize (#observeHost) so the panel always overlays the
|
|
481
|
-
* host correctly even when the host itself sits inside a scrolled
|
|
482
|
-
* ancestor.
|
|
483
|
-
*
|
|
484
|
-
* UNDER A CSS `zoom` (the shell's content zoom scales a tile body),
|
|
485
|
-
* `getBoundingClientRect()` answers in screen pixels, which already
|
|
486
|
-
* carry the zoom, while the panel's own `top`/`left`/`height` are
|
|
487
|
-
* CSS pixels that the same zoom multiplies again. Copied across
|
|
488
|
-
* unchanged, every length was scaled twice: at 150% the panel landed
|
|
489
|
-
* right of and below its host, at 50% it was half the host's height.
|
|
490
|
-
* So the rect is divided by the panel's effective zoom
|
|
491
|
-
* (`currentCSSZoom`: 1 when nothing above it zooms, `undefined` in an
|
|
492
|
-
* engine that predates it), and the width subtracted is the panel's
|
|
493
|
-
* laid-out one, which `min-width` may hold above the requested
|
|
494
|
-
* width. Rounding happens in screen pixels, so at 100% the output is
|
|
495
|
-
* exactly what it always was.
|
|
496
|
-
*
|
|
497
|
-
* NEVER WIDER THAN THE HOST (BUG-0035). The right edge is pinned, so a
|
|
498
|
-
* panel wider than its host (700px by default, `min-width: 400px`)
|
|
499
|
-
* reached past the host's left edge across the neighbouring pane. The
|
|
500
|
-
* host's width, in the panel's CSS pixels, goes on the panel as
|
|
501
|
-
* `--twm-slide-out-host-w`, which the stylesheet uses as `max-width`
|
|
502
|
-
* and as the ceiling of the 400px `min-width`; it is set BEFORE the
|
|
503
|
-
* width is read so the read sees the capped layout, and the width is
|
|
504
|
-
* capped here too for an engine that lays nothing out. */
|
|
505
|
-
#trackGeometry = () => {
|
|
506
|
-
if (!this.#open || !this.#panel || !this.#container) return;
|
|
507
|
-
const r = this.#container.getBoundingClientRect();
|
|
508
|
-
const zoom = this.#panel.currentCSSZoom || 1;
|
|
509
|
-
const hostWidth = r.width / zoom;
|
|
510
|
-
const s = this.#panel.style;
|
|
511
|
-
s.setProperty("--twm-slide-out-host-w", `${hostWidth}px`);
|
|
512
|
-
const width = Math.min(this.#panel.offsetWidth || this.#width, hostWidth);
|
|
513
|
-
s.position = "fixed";
|
|
514
|
-
s.top = `${Math.round(r.top) / zoom}px`;
|
|
515
|
-
s.left = `${Math.round(r.right - width * zoom) / zoom}px`;
|
|
516
|
-
s.right = "auto";
|
|
517
|
-
s.height = `${Math.round(r.height) / zoom}px`;
|
|
518
|
-
};
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
export {
|
|
522
|
-
ComponentBase,
|
|
523
|
-
ActionDropdown,
|
|
524
|
-
SlideOutPanel
|
|
525
|
-
};
|
|
526
|
-
//# sourceMappingURL=chunk-MIW7D6WC.js.map
|