flexdesk 0.16.1 → 0.17.1
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 +112 -0
- package/LICENSE +0 -0
- package/README.md +2435 -42
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/canvas.js +18 -0
- package/charts.js +21 -0
- package/core.js +14 -0
- package/css/base.css +1834 -75
- package/css/flexdesk.css +3165 -159
- package/css/overrides.css +16 -0
- package/css/reset.css +0 -0
- package/css/tokens.css +0 -0
- package/dist/canvas.js +6 -239
- package/dist/canvas.js.map +3 -3
- package/dist/charts.js +707 -18
- package/dist/charts.js.map +4 -4
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-4A7SJC3T.js +503 -0
- package/dist/chunk-4A7SJC3T.js.map +7 -0
- package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
- package/dist/chunk-CVMCZZX2.js.map +7 -0
- package/dist/chunk-D3COLZZ2.js +1311 -0
- package/dist/chunk-D3COLZZ2.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
- package/dist/chunk-GOQLR5WR.js.map +7 -0
- package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
- package/dist/chunk-HNU6MM2B.js.map +7 -0
- package/dist/chunk-HYJYYEPK.js +375 -0
- package/dist/chunk-HYJYYEPK.js.map +7 -0
- package/dist/chunk-JKZLNKG6.js +2041 -0
- package/dist/chunk-JKZLNKG6.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-FBGP5KN5.js → chunk-LWV23YVE.js} +5073 -532
- package/dist/chunk-LWV23YVE.js.map +7 -0
- package/dist/chunk-N7HC3ADV.js +30 -0
- package/dist/chunk-N7HC3ADV.js.map +7 -0
- package/dist/{chunk-RQM5Z2MU.js → chunk-RMTRWWEO.js} +633 -311
- package/dist/chunk-RMTRWWEO.js.map +7 -0
- package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
- package/dist/chunk-UURZ37SD.js.map +7 -0
- package/dist/{chunk-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
- package/dist/chunk-UWO43BZU.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +0 -0
- package/dist/chunk-WVFGV5FT.js.map +0 -0
- package/dist/core.js +170 -118
- package/dist/core.js.map +2 -2
- package/dist/editor.js +156 -70
- package/dist/editor.js.map +2 -2
- package/dist/flexdesk.css +3165 -159
- package/dist/flow.js +2789 -306
- package/dist/flow.js.map +4 -4
- package/dist/host.js +109 -3
- package/dist/host.js.map +3 -3
- 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 +657 -234
- package/dist/tiles.js.map +4 -4
- package/dist/tokens.css +0 -0
- package/dist/widgets.js +3290 -2124
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +1227 -347
- package/dist/wm.js.map +4 -4
- package/editor.js +20 -0
- package/flow.js +21 -0
- package/host.js +14 -0
- package/package.json +12 -1
- package/src/canvas/canvas_adapter.js +5 -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 +1 -0
- package/src/canvas/layered_layout.js +244 -0
- package/src/charts/chart_types.js +0 -0
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +2 -2
- package/src/charts/plot_config.js +53 -29
- package/src/charts/plot_config_editor.js +627 -0
- package/src/charts/plot_grid.js +449 -0
- package/src/charts/plot_popout_window.js +103 -317
- package/src/charts/plotly_wrapper.js +22 -2
- package/src/charts/series_transforms.js +206 -0
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +1 -1
- package/src/core/settings.js +105 -19
- package/src/core/state_guard.js +93 -51
- package/src/core/state_guard_config.js +85 -74
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +3 -3
- package/src/editor/monaco_editor_factory.js +124 -60
- package/src/editor/monaco_loader.js +103 -28
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_tab_bar.js +38 -52
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +6 -3
- package/src/flow/canvas_editor/connections.js +90 -0
- package/src/flow/canvas_editor/editor.js +2351 -153
- package/src/flow/canvas_editor/index.js +2 -1
- package/src/flow/canvas_editor/layout.js +119 -12
- package/src/flow/canvas_editor/strings.js +80 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +97 -3
- 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 +3 -3
- package/src/flow/kit/keys.js +7 -1
- package/src/flow/kit/logic_graph.js +97 -15
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +2 -1
- package/src/flow/kit/step_picker.js +0 -0
- package/src/flow/kit/strings.js +3 -1
- package/src/flow/kit/value_picker.js +0 -0
- package/src/flow/kit/widgets.js +86 -109
- package/src/flow/lanes/editor.js +136 -14
- package/src/flow/lanes/index.js +3 -1
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +44 -2
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +1 -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 +168 -42
- package/src/help/help_registry.js +12 -1
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +117 -0
- package/src/host/host.js +10 -2
- package/src/host/photino_host.js +9 -0
- package/src/host/photino_host.md +21 -3
- package/src/host/pywebview_host.js +10 -0
- package/src/tiles/chart_tiles.js +53 -245
- package/src/tiles/config_schema.js +8 -0
- package/src/tiles/figure_tile.js +322 -0
- package/src/tiles/layout_persistence.js +3 -3
- package/src/tiles/list_tiles.js +7 -7
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_base.js +1 -1
- package/src/tiles/tile_config_form.js +442 -0
- package/src/tiles/tile_grid.js +2 -0
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +67 -99
- package/src/tiling/command_palette.js +64 -19
- package/src/tiling/content_registry.js +4 -1
- package/src/tiling/desktops.js +11 -1
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +403 -128
- package/src/tiling/kind_taxonomy.js +78 -0
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +59 -10
- package/src/tiling/page_factory.js +5 -5
- package/src/tiling/panel_availability.js +49 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +114 -12
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_breadcrumb.js +0 -0
- package/src/tiling/tile_renderer.js +105 -9
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/tile_tree.js +0 -0
- package/src/tiling/wm.js +562 -30
- 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 +30 -2
- 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 +293 -0
- package/src/ui/components/cell_editors.js +480 -0
- package/src/ui/components/cell_grid.js +761 -0
- package/src/ui/components/cell_paste.js +243 -0
- package/src/ui/components/cell_selection.js +220 -0
- package/src/ui/components/choice_controls.js +351 -0
- package/src/ui/components/color_field.js +211 -0
- package/src/ui/components/color_list_field.js +210 -0
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +314 -0
- package/src/ui/components/column_order.js +142 -0
- package/src/ui/components/computing_status_window.js +98 -25
- package/src/ui/components/confirm_dialog.js +81 -38
- package/src/ui/components/context_menu.js +2 -2
- package/src/ui/components/conversation_view.js +440 -0
- package/src/ui/components/crosstab.js +289 -0
- package/src/ui/components/data_table.js +696 -71
- package/src/ui/components/detail_header.js +22 -11
- package/src/ui/components/document_outline.js +397 -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 +322 -0
- package/src/ui/components/gallery_picker.js +19 -8
- package/src/ui/components/header_actions.js +55 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +51 -0
- package/src/ui/components/managed_window.js +27 -4
- package/src/ui/components/menu_bar.js +745 -0
- package/src/ui/components/modal.js +449 -75
- package/src/ui/components/notification_history.js +3 -3
- package/src/ui/components/progress.js +483 -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 +298 -0
- package/src/ui/components/series.js +173 -0
- package/src/ui/components/settings_view.js +634 -0
- package/src/ui/components/slide_out_panel.js +43 -6
- package/src/ui/components/slider_field.js +275 -18
- package/src/ui/components/sortable_list.js +0 -0
- package/src/ui/components/staged_edits.js +361 -0
- package/src/ui/components/staged_history.js +172 -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 +182 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +497 -0
- package/src/ui/components/tag_input.js +215 -0
- package/src/ui/components/toast.js +25 -8
- package/src/ui/components/toggle_field.js +175 -0
- package/src/ui/components/tree_view.js +55 -34
- package/src/ui/components/tsv.js +74 -0
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/wizard.js +563 -0
- package/src/ui/controllers/window_chrome_controller.js +2 -2
- package/src/ui/notification_center.js +375 -119
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/markdown.js +709 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +16 -12
- 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 +55 -0
- package/src/ui/utils/tooltip_service.js +0 -8
- package/tiles.js +27 -0
- package/widgets.js +69 -0
- package/wm.js +28 -0
- package/dist/chunk-6JSOVNID.js.map +0 -7
- package/dist/chunk-B5Q53LAJ.js.map +0 -7
- package/dist/chunk-FBGP5KN5.js.map +0 -7
- package/dist/chunk-FL5KFNQH.js.map +0 -7
- package/dist/chunk-NLTOY57M.js.map +0 -7
- package/dist/chunk-NNFTETK3.js +0 -633
- package/dist/chunk-NNFTETK3.js.map +0 -7
- package/dist/chunk-O5OHMWBB.js +0 -284
- package/dist/chunk-O5OHMWBB.js.map +0 -7
- package/dist/chunk-RQM5Z2MU.js.map +0 -7
- package/dist/chunk-SN4LSOAR.js.map +0 -7
- package/dist/chunk-W6FBJLPW.js +0 -367
- package/dist/chunk-W6FBJLPW.js.map +0 -7
- package/src/ui/controllers/panel_state_machine.js +0 -634
|
@@ -0,0 +1,503 @@
|
|
|
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
|
+
*/
|
|
268
|
+
static position(trigger, menu) {
|
|
269
|
+
if (!trigger || !menu) return;
|
|
270
|
+
Object.assign(menu.style, {
|
|
271
|
+
position: "fixed",
|
|
272
|
+
top: "auto",
|
|
273
|
+
bottom: "auto",
|
|
274
|
+
left: "auto",
|
|
275
|
+
right: "auto",
|
|
276
|
+
maxHeight: "",
|
|
277
|
+
overflowY: ""
|
|
278
|
+
});
|
|
279
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
280
|
+
const menuRect = menu.getBoundingClientRect();
|
|
281
|
+
const vw = window.innerWidth;
|
|
282
|
+
const vh = window.innerHeight;
|
|
283
|
+
const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;
|
|
284
|
+
const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;
|
|
285
|
+
let top;
|
|
286
|
+
let maxHeight;
|
|
287
|
+
if (menuRect.height <= spaceBelow) {
|
|
288
|
+
top = triggerRect.bottom + GAP;
|
|
289
|
+
maxHeight = spaceBelow;
|
|
290
|
+
} else if (menuRect.height <= spaceAbove) {
|
|
291
|
+
top = triggerRect.top - GAP - menuRect.height;
|
|
292
|
+
maxHeight = spaceAbove;
|
|
293
|
+
} else if (spaceBelow >= spaceAbove) {
|
|
294
|
+
top = triggerRect.bottom + GAP;
|
|
295
|
+
maxHeight = spaceBelow;
|
|
296
|
+
} else {
|
|
297
|
+
maxHeight = spaceAbove;
|
|
298
|
+
top = EDGE_MARGIN;
|
|
299
|
+
}
|
|
300
|
+
let left = triggerRect.left;
|
|
301
|
+
if (left + menuRect.width > vw - EDGE_MARGIN) {
|
|
302
|
+
left = triggerRect.right - menuRect.width;
|
|
303
|
+
}
|
|
304
|
+
left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));
|
|
305
|
+
Object.assign(menu.style, {
|
|
306
|
+
position: "fixed",
|
|
307
|
+
top: `${Math.round(top)}px`,
|
|
308
|
+
left: `${Math.round(left)}px`,
|
|
309
|
+
maxHeight: `${Math.round(maxHeight)}px`,
|
|
310
|
+
overflowY: "auto"
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/ui/components/slide_out_panel.js
|
|
316
|
+
var SlideOutPanel = class extends ComponentBase {
|
|
317
|
+
/** @type {HTMLElement} */
|
|
318
|
+
#container = null;
|
|
319
|
+
/** @type {HTMLElement} */
|
|
320
|
+
#backdrop = null;
|
|
321
|
+
/** @type {HTMLElement} */
|
|
322
|
+
#panel = null;
|
|
323
|
+
/** @type {HTMLElement} */
|
|
324
|
+
#headerIcon = null;
|
|
325
|
+
/** @type {HTMLElement} */
|
|
326
|
+
#headerTitle = null;
|
|
327
|
+
/** @type {HTMLElement} */
|
|
328
|
+
#body = null;
|
|
329
|
+
/** @type {number} */
|
|
330
|
+
#width;
|
|
331
|
+
/** @type {Function|null} */
|
|
332
|
+
#onClose;
|
|
333
|
+
/** @type {boolean} */
|
|
334
|
+
#open = false;
|
|
335
|
+
/** @type {ResizeObserver|null} */
|
|
336
|
+
#hostObserver = null;
|
|
337
|
+
/**
|
|
338
|
+
* @param {object} opts
|
|
339
|
+
* @param {number} [opts.width=380] Panel width in px.
|
|
340
|
+
* @param {Function} [opts.onClose] Called after the panel closes.
|
|
341
|
+
*/
|
|
342
|
+
constructor({ width = 700, onClose } = {}) {
|
|
343
|
+
super();
|
|
344
|
+
this.#width = width;
|
|
345
|
+
this.#onClose = onClose ?? null;
|
|
346
|
+
}
|
|
347
|
+
// ─── Lifecycle ──────────────────────────────────────────────────────────
|
|
348
|
+
/**
|
|
349
|
+
* Mount the panel DOM into a host container.
|
|
350
|
+
* @param {HTMLElement} container Must have position:relative; overflow:hidden.
|
|
351
|
+
*/
|
|
352
|
+
mount(container) {
|
|
353
|
+
if (this._mounted) return;
|
|
354
|
+
this.#container = container;
|
|
355
|
+
this.#backdrop = document.createElement("div");
|
|
356
|
+
this.#backdrop.className = "twm-slide-out-panel__backdrop";
|
|
357
|
+
this.#backdrop.addEventListener("click", this.#handleBackdropClick);
|
|
358
|
+
this.#panel = document.createElement("div");
|
|
359
|
+
this.#panel.className = "twm-slide-out-panel";
|
|
360
|
+
this.#panel.style.width = `${this.#width}px`;
|
|
361
|
+
const header = document.createElement("div");
|
|
362
|
+
header.className = "twm-slide-out-panel__header";
|
|
363
|
+
this.#headerIcon = document.createElement("span");
|
|
364
|
+
this.#headerIcon.className = "material-symbols-outlined";
|
|
365
|
+
this.#headerTitle = document.createElement("span");
|
|
366
|
+
this.#headerTitle.className = "twm-slide-out-panel__title";
|
|
367
|
+
const closeBtn = document.createElement("button");
|
|
368
|
+
closeBtn.className = "twm-slide-out-panel__close twm-btn-icon";
|
|
369
|
+
closeBtn.type = "button";
|
|
370
|
+
closeBtn.title = "Close";
|
|
371
|
+
closeBtn.innerHTML = '<span class="material-symbols-outlined">close</span>';
|
|
372
|
+
closeBtn.addEventListener("click", this.#handleCloseClick);
|
|
373
|
+
header.append(this.#headerIcon, this.#headerTitle, closeBtn);
|
|
374
|
+
this.#body = document.createElement("div");
|
|
375
|
+
this.#body.className = "twm-slide-out-panel__body";
|
|
376
|
+
this.#panel.append(header, this.#body);
|
|
377
|
+
this.#container.append(this.#backdrop, this.#panel);
|
|
378
|
+
this._mounted = true;
|
|
379
|
+
}
|
|
380
|
+
dispose() {
|
|
381
|
+
this.close();
|
|
382
|
+
this.#backdrop?.remove();
|
|
383
|
+
this.#panel?.remove();
|
|
384
|
+
this.#backdrop = null;
|
|
385
|
+
this.#panel = null;
|
|
386
|
+
this.#body = null;
|
|
387
|
+
this.#container = null;
|
|
388
|
+
super.dispose();
|
|
389
|
+
}
|
|
390
|
+
// ─── Public API ─────────────────────────────────────────────────────────
|
|
391
|
+
/** The inner content element — callers render config into this. */
|
|
392
|
+
get contentEl() {
|
|
393
|
+
return this.#body;
|
|
394
|
+
}
|
|
395
|
+
get isOpen() {
|
|
396
|
+
return this.#open;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Open (or re-open) the panel with a new title/icon.
|
|
400
|
+
* Clears previous content. Returns contentEl for rendering.
|
|
401
|
+
* @param {string} title
|
|
402
|
+
* @param {string} icon Material Symbols icon name.
|
|
403
|
+
* @returns {HTMLElement} contentEl
|
|
404
|
+
*/
|
|
405
|
+
open(title, icon) {
|
|
406
|
+
if (!this._mounted) return null;
|
|
407
|
+
this.#body.innerHTML = "";
|
|
408
|
+
this.#headerIcon.textContent = icon || "";
|
|
409
|
+
this.#headerIcon.style.display = icon ? "" : "none";
|
|
410
|
+
this.#headerTitle.textContent = title || "";
|
|
411
|
+
if (!this.#open) {
|
|
412
|
+
this.#open = true;
|
|
413
|
+
this.#backdrop.classList.add("twm-slide-out-panel__backdrop--visible");
|
|
414
|
+
this.#panel.classList.add("twm-slide-out-panel--open");
|
|
415
|
+
document.addEventListener("keydown", this.#handleEsc);
|
|
416
|
+
this.#trackGeometry();
|
|
417
|
+
window.addEventListener("scroll", this.#trackGeometry, true);
|
|
418
|
+
window.addEventListener("resize", this.#trackGeometry);
|
|
419
|
+
this.#observeHost();
|
|
420
|
+
}
|
|
421
|
+
return this.#body;
|
|
422
|
+
}
|
|
423
|
+
/** Close the panel and clear content. */
|
|
424
|
+
close() {
|
|
425
|
+
if (!this.#open) return;
|
|
426
|
+
this.#open = false;
|
|
427
|
+
this.#panel.classList.remove("twm-slide-out-panel--open");
|
|
428
|
+
this.#backdrop.classList.remove("twm-slide-out-panel__backdrop--visible");
|
|
429
|
+
document.removeEventListener("keydown", this.#handleEsc);
|
|
430
|
+
window.removeEventListener("scroll", this.#trackGeometry, true);
|
|
431
|
+
window.removeEventListener("resize", this.#trackGeometry);
|
|
432
|
+
this.#hostObserver?.disconnect();
|
|
433
|
+
this.#hostObserver = null;
|
|
434
|
+
this.#body.innerHTML = "";
|
|
435
|
+
this.#onClose?.();
|
|
436
|
+
}
|
|
437
|
+
// ─── Event handlers (arrow fns for stable `this`) ───────────────────────
|
|
438
|
+
#handleBackdropClick = () => {
|
|
439
|
+
this.close();
|
|
440
|
+
};
|
|
441
|
+
#handleCloseClick = () => {
|
|
442
|
+
this.close();
|
|
443
|
+
};
|
|
444
|
+
#handleEsc = (e) => {
|
|
445
|
+
if (e.key === "Escape") {
|
|
446
|
+
e.stopPropagation();
|
|
447
|
+
this.close();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
/** The host moves or resizes without any window event when a tile
|
|
451
|
+
* splitter is dragged or a dock panel toggles, and the fixed geometry
|
|
452
|
+
* would then stay at the old coordinates. Observe the host AND each
|
|
453
|
+
* ancestor: a host can move without changing size of its own (a dock
|
|
454
|
+
* panel toggling beside a fixed-width tile shrinks the content area,
|
|
455
|
+
* an ancestor, and the tile only shifts). The callback runs
|
|
456
|
+
* synchronously, after layout and before paint, so the panel follows
|
|
457
|
+
* a drag without a frame of lag; it only writes the panel's own
|
|
458
|
+
* `position: fixed` geometry, which is out of flow and resizes
|
|
459
|
+
* nothing observed, so it cannot loop. */
|
|
460
|
+
#observeHost() {
|
|
461
|
+
this.#hostObserver = new ResizeObserver(this.#trackGeometry);
|
|
462
|
+
for (let el = this.#container; el; el = el.parentElement) {
|
|
463
|
+
this.#hostObserver.observe(el);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/** Pin the panel to the right edge of its host's *visible* frame
|
|
467
|
+
* using `position: fixed`. Re-run on every window scroll/resize and
|
|
468
|
+
* every host resize (#observeHost) so the panel always overlays the
|
|
469
|
+
* host correctly even when the host itself sits inside a scrolled
|
|
470
|
+
* ancestor.
|
|
471
|
+
*
|
|
472
|
+
* UNDER A CSS `zoom` (the shell's content zoom scales a tile body),
|
|
473
|
+
* `getBoundingClientRect()` answers in screen pixels, which already
|
|
474
|
+
* carry the zoom, while the panel's own `top`/`left`/`height` are
|
|
475
|
+
* CSS pixels that the same zoom multiplies again. Copied across
|
|
476
|
+
* unchanged, every length was scaled twice: at 150% the panel landed
|
|
477
|
+
* right of and below its host, at 50% it was half the host's height.
|
|
478
|
+
* So the rect is divided by the panel's effective zoom
|
|
479
|
+
* (`currentCSSZoom`: 1 when nothing above it zooms, `undefined` in an
|
|
480
|
+
* engine that predates it), and the width subtracted is the panel's
|
|
481
|
+
* laid-out one, which `min-width` may hold above the requested
|
|
482
|
+
* width. Rounding happens in screen pixels, so at 100% the output is
|
|
483
|
+
* exactly what it always was. */
|
|
484
|
+
#trackGeometry = () => {
|
|
485
|
+
if (!this.#open || !this.#panel || !this.#container) return;
|
|
486
|
+
const r = this.#container.getBoundingClientRect();
|
|
487
|
+
const zoom = this.#panel.currentCSSZoom || 1;
|
|
488
|
+
const width = this.#panel.offsetWidth || this.#width;
|
|
489
|
+
const s = this.#panel.style;
|
|
490
|
+
s.position = "fixed";
|
|
491
|
+
s.top = `${Math.round(r.top) / zoom}px`;
|
|
492
|
+
s.left = `${Math.round(r.right - width * zoom) / zoom}px`;
|
|
493
|
+
s.right = "auto";
|
|
494
|
+
s.height = `${Math.round(r.height) / zoom}px`;
|
|
495
|
+
};
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
export {
|
|
499
|
+
ComponentBase,
|
|
500
|
+
ActionDropdown,
|
|
501
|
+
SlideOutPanel
|
|
502
|
+
};
|
|
503
|
+
//# sourceMappingURL=chunk-4A7SJC3T.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/ui/base/component_base.js", "../src/ui/components/action_dropdown.js", "../src/ui/components/slide_out_panel.js"],
|
|
4
|
+
"sourcesContent": ["/**\r\n * ComponentBase (js_new)\r\n *\r\n * Ultra-lightweight base for leaf UI widgets rendered inside controllers/panels.\r\n * Handles props/state updates and deterministic teardown without storing data on\r\n * DOM elements.\r\n */\r\nexport class ComponentBase {\r\n constructor({ eventBus, logger } = {}) {\r\n if (new.target === ComponentBase) {\r\n throw new Error('ComponentBase is abstract \u2013 extend it instead');\r\n }\r\n\r\n this.eventBus = eventBus;\r\n this.logger = logger;\r\n this._mounted = false;\r\n }\r\n\r\n mount(/* container, props */) {\r\n throw new Error('ComponentBase.mount must be implemented by subclasses');\r\n }\r\n\r\n update(/* props */) {\r\n // default no-op for stateless components\r\n }\r\n\r\n dispose() {\r\n this._mounted = false;\r\n }\r\n}\r\n", "/**\n * action_dropdown.js\n *\n * Reusable dropdown component for action buttons (Add Scenario, Add Widget, etc).\n * Handles opening, closing, auto-positioning, keyboard navigation, and outside clicks.\n *\n * Auto-positioning: measures available viewport space around the trigger element\n * and picks the best direction (above/below) and alignment (left/right).\n * Adds scrolling when the menu would exceed available vertical space.\n */\n\n/** Viewport margin in px \u2014 dropdown stays this far from edges. */\nconst EDGE_MARGIN = 8;\n/** Gap between trigger and dropdown in px. */\nconst GAP = 8;\n\n/**\n * ActionDropdown - A reusable dropdown component with smart auto-positioning.\n *\n * Usage:\n * ```javascript\n * const dropdown = new ActionDropdown({\n * trigger: buttonElement,\n * options: [\n * { type: 'option1', label: 'Option 1', icon: 'icon_name', description: 'Description' },\n * { separator: true },\n * { type: 'option2', label: 'Option 2', icon: 'icon_name', description: 'Description' }\n * ],\n * onSelect: (option) => console.log('Selected:', option.type),\n * });\n *\n * // Control programmatically\n * dropdown.open();\n * dropdown.close();\n * dropdown.toggle();\n * dropdown.destroy();\n * ```\n *\n * \u2500\u2500 Separators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * An entry `{ separator: true }` draws a divider instead of a button: a\n * `<div role=\"separator\" class=\"twm-action-dropdown-separator\">`, which takes\n * no focus, is never reached by the arrow keys (they move between the option\n * buttons only) and never reaches `onSelect` (it has no click handler). A\n * separator only separates: one at the start or the end of the list, or one\n * right after another, is dropped, so a list assembled from groups that may\n * come out empty never draws a stray or doubled rule.\n */\nexport class ActionDropdown {\n /**\n * @param {Object} config\n * @param {HTMLElement} config.trigger - Button element that triggers the dropdown\n * @param {Array} config.options - Array of option objects with { type, label, icon, description? },\n * or `{ separator: true }` for a divider between them\n * @param {Function} config.onSelect - Callback when an option is selected\n * @param {string} [config.className=''] - Additional CSS class for the dropdown\n * @param {string} [config.menuId] - Optional ID for the menu element\n */\n constructor(config) {\n this.trigger = config.trigger;\n this.options = config.options || [];\n this.onSelect = config.onSelect;\n this.className = config.className || '';\n this.menuId = config.menuId;\n\n this.isOpen = false;\n this.menuEl = null;\n\n this._boundHandleDocumentClick = this._handleDocumentClick.bind(this);\n this._boundHandleKeydown = this._handleKeydown.bind(this);\n this._boundHandleTriggerClick = this._handleTriggerClick.bind(this);\n\n this._init();\n }\n\n /**\n * Initialize the dropdown.\n * @private\n */\n _init() {\n if (!this.trigger) {\n console.warn('[ActionDropdown] No trigger element provided');\n return;\n }\n\n this._createMenu();\n this.trigger.addEventListener('click', this._boundHandleTriggerClick);\n }\n\n /**\n * Create the dropdown menu element.\n * @private\n */\n _createMenu() {\n const menu = document.createElement('div');\n menu.className = `twm-action-dropdown-menu ${this.className}`.trim();\n menu.setAttribute('role', 'menu');\n menu.hidden = true;\n\n if (this.menuId) {\n menu.id = this.menuId;\n }\n\n this._renderOptions(menu);\n\n // Append to body so position:fixed works without clipping.\n document.body.appendChild(menu);\n\n this.menuEl = menu;\n }\n\n /**\n * Render option buttons, and dividers for separator entries, into a\n * container element. `data-index` stays the entry's index in `options`.\n * @param {HTMLElement} container\n * @private\n */\n _renderOptions(container) {\n container.innerHTML = '';\n let pendingSeparator = false;\n this.options.forEach((opt, index) => {\n if (opt?.separator) {\n // Drawn only once an option follows it, and only if an option\n // came before it: leading, trailing and doubled separators\n // collapse.\n pendingSeparator = container.childElementCount > 0;\n return;\n }\n if (pendingSeparator) {\n const sep = document.createElement('div');\n sep.className = 'twm-action-dropdown-separator';\n sep.setAttribute('role', 'separator');\n container.appendChild(sep);\n pendingSeparator = false;\n }\n\n const optionBtn = document.createElement('button');\n optionBtn.type = 'button';\n optionBtn.className = 'twm-action-dropdown-option';\n optionBtn.dataset.index = index;\n optionBtn.dataset.type = opt.type;\n optionBtn.setAttribute('role', 'menuitem');\n\n const iconHtml = opt.icon\n ? `<span class=\"material-symbols-outlined twm-option-icon\">${opt.icon}</span>`\n : '';\n\n const descHtml = opt.description\n ? `<span class=\"twm-option-desc\">${opt.description}</span>`\n : '';\n\n optionBtn.innerHTML = `\n ${iconHtml}\n <span class=\"option-text\">\n <span class=\"twm-option-label\">${opt.label}</span>\n ${descHtml}\n </span>\n `;\n\n optionBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n this._selectOption(opt);\n });\n\n container.appendChild(optionBtn);\n });\n }\n\n /**\n * Handle trigger button click.\n * @param {MouseEvent} e\n * @private\n */\n _handleTriggerClick(e) {\n e.preventDefault();\n e.stopPropagation();\n this.toggle();\n }\n\n /**\n * Toggle the dropdown open/closed.\n * @param {boolean} [forceState] - Optional forced state\n */\n toggle(forceState) {\n const shouldOpen = typeof forceState === 'boolean' ? forceState : !this.isOpen;\n if (shouldOpen) {\n this.open();\n } else {\n this.close();\n }\n }\n\n /**\n * Open the dropdown with auto-positioning.\n */\n open() {\n if (this.isOpen || !this.menuEl) return;\n\n this.isOpen = true;\n this.menuEl.hidden = false;\n\n // Position against trigger\n ActionDropdown.position(this.trigger, this.menuEl);\n\n // Animate in.\n //\n // OPTIONAL CHAINING, AND IT IS LOAD-BEARING. This callback runs a frame\n // after `open()` returned, and `destroy()` sets `this.menuEl = null`\n // (see below) \u2014 so a dropdown that is opened and then destroyed inside\n // one frame threw an uncaught `TypeError: Cannot read properties of\n // null` out of an animation-frame callback, where no caller has a stack\n // to catch it. That is not a hypothetical: it is what a user does every\n // time they open a picker and then click something that unmounts the\n // pane around it, and it was reproduced eighteen times in one run of a\n // consumer's settings suite. Nothing is lost by skipping the class \u2014 the\n // element it would have been added to no longer exists.\n requestAnimationFrame(() => {\n this.menuEl?.classList.add('visible');\n });\n\n // Update trigger state\n this.trigger?.classList.add('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'true');\n }\n\n // Add document listeners\n document.addEventListener('click', this._boundHandleDocumentClick, true);\n document.addEventListener('keydown', this._boundHandleKeydown);\n\n // Focus first option for keyboard accessibility\n const firstOption = this.menuEl.querySelector('.twm-action-dropdown-option');\n firstOption?.focus();\n }\n\n /**\n * Close the dropdown.\n */\n close() {\n if (!this.isOpen) return;\n\n this.isOpen = false;\n\n if (this.menuEl) {\n this.menuEl.classList.remove('visible');\n this.menuEl.hidden = true;\n }\n\n // Update trigger state. `aria-expanded` belongs on the TRIGGER and has\n // to be written on every close, not only on the ones a click caused \u2014\n // an embedder that synced it from its own click handler was announcing\n // an expanded menu to a screen reader every time Escape or an outside\n // click dismissed one. The component knows when it closed; nothing else\n // reliably does.\n this.trigger?.classList.remove('twm-is-open');\n if (this.trigger?.hasAttribute('aria-expanded')) {\n this.trigger.setAttribute('aria-expanded', 'false');\n }\n\n // Remove document listeners\n document.removeEventListener('click', this._boundHandleDocumentClick, true);\n document.removeEventListener('keydown', this._boundHandleKeydown);\n }\n\n /**\n * Handle document click (close on outside click).\n * @param {MouseEvent} e\n * @private\n */\n _handleDocumentClick(e) {\n if (!this.isOpen) return;\n\n // Don't close if clicking inside dropdown or trigger\n if (this.menuEl?.contains(e.target) || this.trigger?.contains(e.target)) {\n return;\n }\n\n this.close();\n }\n\n /**\n * Handle keyboard events.\n * @param {KeyboardEvent} e\n * @private\n */\n _handleKeydown(e) {\n if (e.key === 'Escape') {\n // AND NOBODY ELSE GETS IT. An open dropdown is the innermost thing\n // on screen, so Escape means \"close this\" and nothing further \u2014\n // but the event was left to bubble, and inside a `ManagedWindow`\n // (which binds its own Escape to dismiss) that meant one keystroke\n // closed the dropdown AND the dialog around it. The user loses a\n // form they were filling in because they changed their mind about\n // one field.\n e.preventDefault();\n e.stopPropagation();\n this.close();\n this.trigger?.focus();\n return;\n }\n\n // Arrow key navigation\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n this._navigateOptions(e.key === 'ArrowDown' ? 1 : -1);\n }\n }\n\n /**\n * Navigate options with arrow keys.\n * @param {number} direction - 1 for down, -1 for up\n * @private\n */\n _navigateOptions(direction) {\n const options = this.menuEl?.querySelectorAll('.twm-action-dropdown-option');\n if (!options || options.length === 0) return;\n\n const currentIndex = Array.from(options).findIndex(opt => opt === document.activeElement);\n let nextIndex = currentIndex + direction;\n\n if (nextIndex < 0) nextIndex = options.length - 1;\n if (nextIndex >= options.length) nextIndex = 0;\n\n options[nextIndex]?.focus();\n }\n\n /**\n * Handle option selection.\n * @param {Object} option - The selected option\n * @private\n */\n _selectOption(option) {\n this.close();\n\n if (this.onSelect) {\n this.onSelect(option);\n }\n }\n\n /**\n * Update the options dynamically.\n * @param {Array} newOptions - New options array\n */\n setOptions(newOptions) {\n this.options = newOptions;\n if (this.menuEl) {\n this._renderOptions(this.menuEl);\n }\n }\n\n /**\n * Destroy the dropdown and clean up.\n */\n destroy() {\n this.close();\n\n // Remove trigger listener\n this.trigger?.removeEventListener('click', this._boundHandleTriggerClick);\n\n // Remove menu element\n this.menuEl?.remove();\n this.menuEl = null;\n\n this.trigger = null;\n this.options = [];\n this.onSelect = null;\n }\n\n // =========================================================================\n // STATIC \u2014 shared auto-positioning for any trigger + menu pair\n // =========================================================================\n\n /**\n * Position a dropdown menu relative to a trigger element.\n * Uses `position: fixed` to avoid overflow clipping.\n * Picks above/below based on available space, aligns left/right edge,\n * and applies `max-height` + `overflow-y: auto` when the menu is taller\n * than the available space.\n *\n * @param {HTMLElement} trigger - The element the dropdown opens from\n * @param {HTMLElement} menu - The dropdown menu element to position\n */\n static position(trigger, menu) {\n if (!trigger || !menu) return;\n\n // Reset any prior inline position so natural size can be measured.\n Object.assign(menu.style, {\n position: 'fixed',\n top: 'auto',\n bottom: 'auto',\n left: 'auto',\n right: 'auto',\n maxHeight: '',\n overflowY: ''\n });\n\n const triggerRect = trigger.getBoundingClientRect();\n const menuRect = menu.getBoundingClientRect();\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // \u2500\u2500 Vertical: prefer below, flip above if insufficient room \u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const spaceBelow = vh - triggerRect.bottom - GAP - EDGE_MARGIN;\n const spaceAbove = triggerRect.top - GAP - EDGE_MARGIN;\n\n let top;\n let maxHeight;\n\n if (menuRect.height <= spaceBelow) {\n // Fits below \u2014 use natural height.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else if (menuRect.height <= spaceAbove) {\n // Fits above \u2014 use natural height.\n top = triggerRect.top - GAP - menuRect.height;\n maxHeight = spaceAbove;\n } else if (spaceBelow >= spaceAbove) {\n // More space below \u2014 scroll.\n top = triggerRect.bottom + GAP;\n maxHeight = spaceBelow;\n } else {\n // More space above \u2014 scroll.\n maxHeight = spaceAbove;\n top = EDGE_MARGIN;\n }\n\n // \u2500\u2500 Horizontal: align left-edge with trigger, flip if off-screen \u2500\u2500\n let left = triggerRect.left;\n\n if (left + menuRect.width > vw - EDGE_MARGIN) {\n // Align right edges instead.\n left = triggerRect.right - menuRect.width;\n }\n\n // Still off-screen? Clamp to edges.\n left = Math.max(EDGE_MARGIN, Math.min(left, vw - menuRect.width - EDGE_MARGIN));\n\n Object.assign(menu.style, {\n position: 'fixed',\n top: `${Math.round(top)}px`,\n left: `${Math.round(left)}px`,\n maxHeight: `${Math.round(maxHeight)}px`,\n overflowY: 'auto'\n });\n }\n}\n\nexport default ActionDropdown;\n", "/**\n * SlideOutPanel\n *\n * Reusable inline slide-out config panel that overlays its host container\n * from the right edge. Used by pages to show element-specific config\n * (plot config, widget config, ETL node config) without the global right panel.\n *\n * Host container must have `position: relative; overflow: hidden`.\n */\nimport { ComponentBase } from '../base/component_base.js';\n\nexport class SlideOutPanel extends ComponentBase {\n\n /** @type {HTMLElement} */ #container = null;\n /** @type {HTMLElement} */ #backdrop = null;\n /** @type {HTMLElement} */ #panel = null;\n /** @type {HTMLElement} */ #headerIcon = null;\n /** @type {HTMLElement} */ #headerTitle = null;\n /** @type {HTMLElement} */ #body = null;\n /** @type {number} */ #width;\n /** @type {Function|null} */#onClose;\n /** @type {boolean} */ #open = false;\n /** @type {ResizeObserver|null} */ #hostObserver = null;\n\n /**\n * @param {object} opts\n * @param {number} [opts.width=380] Panel width in px.\n * @param {Function} [opts.onClose] Called after the panel closes.\n */\n constructor({ width = 700, onClose } = {}) {\n super();\n this.#width = width;\n this.#onClose = onClose ?? null;\n }\n\n // \u2500\u2500\u2500 Lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Mount the panel DOM into a host container.\n * @param {HTMLElement} container Must have position:relative; overflow:hidden.\n */\n mount(container) {\n if (this._mounted) return;\n this.#container = container;\n\n // Backdrop (transparent click-catcher)\n this.#backdrop = document.createElement('div');\n this.#backdrop.className = 'twm-slide-out-panel__backdrop';\n this.#backdrop.addEventListener('click', this.#handleBackdropClick);\n\n // Panel\n this.#panel = document.createElement('div');\n this.#panel.className = 'twm-slide-out-panel';\n this.#panel.style.width = `${this.#width}px`;\n\n // Header\n const header = document.createElement('div');\n header.className = 'twm-slide-out-panel__header';\n\n this.#headerIcon = document.createElement('span');\n this.#headerIcon.className = 'material-symbols-outlined';\n\n this.#headerTitle = document.createElement('span');\n this.#headerTitle.className = 'twm-slide-out-panel__title';\n\n const closeBtn = document.createElement('button');\n closeBtn.className = 'twm-slide-out-panel__close twm-btn-icon';\n closeBtn.type = 'button';\n closeBtn.title = 'Close';\n closeBtn.innerHTML = '<span class=\"material-symbols-outlined\">close</span>';\n closeBtn.addEventListener('click', this.#handleCloseClick);\n\n header.append(this.#headerIcon, this.#headerTitle, closeBtn);\n\n // Body (scrollable content area)\n this.#body = document.createElement('div');\n this.#body.className = 'twm-slide-out-panel__body';\n\n this.#panel.append(header, this.#body);\n this.#container.append(this.#backdrop, this.#panel);\n\n this._mounted = true;\n }\n\n dispose() {\n this.close();\n this.#backdrop?.remove();\n this.#panel?.remove();\n this.#backdrop = null;\n this.#panel = null;\n this.#body = null;\n this.#container = null;\n super.dispose();\n }\n\n // \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /** The inner content element \u2014 callers render config into this. */\n get contentEl() { return this.#body; }\n\n get isOpen() { return this.#open; }\n\n /**\n * Open (or re-open) the panel with a new title/icon.\n * Clears previous content. Returns contentEl for rendering.\n * @param {string} title\n * @param {string} icon Material Symbols icon name.\n * @returns {HTMLElement} contentEl\n */\n open(title, icon) {\n if (!this._mounted) return null;\n\n this.#body.innerHTML = '';\n this.#headerIcon.textContent = icon || '';\n this.#headerIcon.style.display = icon ? '' : 'none';\n this.#headerTitle.textContent = title || '';\n\n if (!this.#open) {\n this.#open = true;\n this.#backdrop.classList.add('twm-slide-out-panel__backdrop--visible');\n this.#panel.classList.add('twm-slide-out-panel--open');\n document.addEventListener('keydown', this.#handleEsc);\n\n // The host may be a scroll container \u2014 absolutely-positioned\n // descendants of one DO scroll along with the content\n // (Web spec: a position:absolute element is clipped + offset\n // by its containing scroll context). We escape that by\n // switching the panel to `position: fixed` while open and\n // tracking the host's bounding rect on scroll / resize, so\n // the panel stays pinned to the host's visible frame at\n // full height regardless of how the user scrolls inside.\n this.#trackGeometry();\n window.addEventListener('scroll', this.#trackGeometry, true);\n window.addEventListener('resize', this.#trackGeometry);\n this.#observeHost();\n }\n\n return this.#body;\n }\n\n /** Close the panel and clear content. */\n close() {\n if (!this.#open) return;\n this.#open = false;\n\n this.#panel.classList.remove('twm-slide-out-panel--open');\n this.#backdrop.classList.remove('twm-slide-out-panel__backdrop--visible');\n document.removeEventListener('keydown', this.#handleEsc);\n window.removeEventListener('scroll', this.#trackGeometry, true);\n window.removeEventListener('resize', this.#trackGeometry);\n this.#hostObserver?.disconnect();\n this.#hostObserver = null;\n // Leave the inline position:fixed geometry in place. The panel\n // slides off-screen via the transform animation (translateX\n // 100%); the next open() re-runs #trackGeometry which will\n // refresh top/left/height from the host's current rect.\n\n this.#body.innerHTML = '';\n this.#onClose?.();\n }\n\n // \u2500\u2500\u2500 Event handlers (arrow fns for stable `this`) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #handleBackdropClick = () => { this.close(); };\n\n #handleCloseClick = () => { this.close(); };\n\n #handleEsc = (e) => {\n if (e.key === 'Escape') {\n e.stopPropagation();\n this.close();\n }\n };\n\n /** The host moves or resizes without any window event when a tile\n * splitter is dragged or a dock panel toggles, and the fixed geometry\n * would then stay at the old coordinates. Observe the host AND each\n * ancestor: a host can move without changing size of its own (a dock\n * panel toggling beside a fixed-width tile shrinks the content area,\n * an ancestor, and the tile only shifts). The callback runs\n * synchronously, after layout and before paint, so the panel follows\n * a drag without a frame of lag; it only writes the panel's own\n * `position: fixed` geometry, which is out of flow and resizes\n * nothing observed, so it cannot loop. */\n #observeHost() {\n this.#hostObserver = new ResizeObserver(this.#trackGeometry);\n for (let el = this.#container; el; el = el.parentElement) {\n this.#hostObserver.observe(el);\n }\n }\n\n /** Pin the panel to the right edge of its host's *visible* frame\n * using `position: fixed`. Re-run on every window scroll/resize and\n * every host resize (#observeHost) so the panel always overlays the\n * host correctly even when the host itself sits inside a scrolled\n * ancestor.\n *\n * UNDER A CSS `zoom` (the shell's content zoom scales a tile body),\n * `getBoundingClientRect()` answers in screen pixels, which already\n * carry the zoom, while the panel's own `top`/`left`/`height` are\n * CSS pixels that the same zoom multiplies again. Copied across\n * unchanged, every length was scaled twice: at 150% the panel landed\n * right of and below its host, at 50% it was half the host's height.\n * So the rect is divided by the panel's effective zoom\n * (`currentCSSZoom`: 1 when nothing above it zooms, `undefined` in an\n * engine that predates it), and the width subtracted is the panel's\n * laid-out one, which `min-width` may hold above the requested\n * width. Rounding happens in screen pixels, so at 100% the output is\n * exactly what it always was. */\n #trackGeometry = () => {\n if (!this.#open || !this.#panel || !this.#container) return;\n const r = this.#container.getBoundingClientRect();\n const zoom = this.#panel.currentCSSZoom || 1;\n const width = this.#panel.offsetWidth || this.#width;\n const s = this.#panel.style;\n s.position = 'fixed';\n s.top = `${Math.round(r.top) / zoom}px`;\n s.left = `${Math.round(r.right - width * zoom) / zoom}px`;\n s.right = 'auto';\n s.height = `${Math.round(r.height) / zoom}px`;\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|