@kispace-io/core 0.7.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/dist/api/base-classes.d.ts +7 -0
- package/dist/api/base-classes.d.ts.map +1 -0
- package/dist/api/constants.d.ts +2 -0
- package/dist/api/constants.d.ts.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +80 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/services.d.ts +27 -0
- package/dist/api/services.d.ts.map +1 -0
- package/dist/api/types.d.ts +11 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/commands/files.d.ts +2 -0
- package/dist/commands/files.d.ts.map +1 -0
- package/dist/commands/global.d.ts +1 -0
- package/dist/commands/global.d.ts.map +1 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/version-info.d.ts +2 -0
- package/dist/commands/version-info.d.ts.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/k-app-selector.d.ts +17 -0
- package/dist/components/k-app-selector.d.ts.map +1 -0
- package/dist/components/k-app-switcher.d.ts +13 -0
- package/dist/components/k-app-switcher.d.ts.map +1 -0
- package/dist/components/k-command.d.ts +31 -0
- package/dist/components/k-command.d.ts.map +1 -0
- package/dist/components/k-extensions.d.ts +32 -0
- package/dist/components/k-extensions.d.ts.map +1 -0
- package/dist/components/k-fastviews.d.ts +34 -0
- package/dist/components/k-fastviews.d.ts.map +1 -0
- package/dist/components/k-filebrowser.d.ts +40 -0
- package/dist/components/k-filebrowser.d.ts.map +1 -0
- package/dist/components/k-language-selector.d.ts +12 -0
- package/dist/components/k-language-selector.d.ts.map +1 -0
- package/dist/components/k-log-terminal.d.ts +36 -0
- package/dist/components/k-log-terminal.d.ts.map +1 -0
- package/dist/components/k-part-name.d.ts +12 -0
- package/dist/components/k-part-name.d.ts.map +1 -0
- package/dist/components/k-tasks.d.ts +13 -0
- package/dist/components/k-tasks.d.ts.map +1 -0
- package/dist/components/k-workspace-name.d.ts +14 -0
- package/dist/components/k-workspace-name.d.ts.map +1 -0
- package/dist/contributions/default-ui-contributions.d.ts +2 -0
- package/dist/contributions/default-ui-contributions.d.ts.map +1 -0
- package/dist/contributions/index.d.ts +1 -0
- package/dist/contributions/index.d.ts.map +1 -0
- package/dist/contributions/marketplace-catalog-contributions.d.ts +2 -0
- package/dist/contributions/marketplace-catalog-contributions.d.ts.map +1 -0
- package/dist/core/app-host-config.d.ts +7 -0
- package/dist/core/app-host-config.d.ts.map +1 -0
- package/dist/core/apploader.d.ts +214 -0
- package/dist/core/apploader.d.ts.map +1 -0
- package/dist/core/appstate.d.ts +12 -0
- package/dist/core/appstate.d.ts.map +1 -0
- package/dist/core/commandregistry.d.ts +79 -0
- package/dist/core/commandregistry.d.ts.map +1 -0
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/constants.d.ts +21 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/contributionregistry.d.ts +49 -0
- package/dist/core/contributionregistry.d.ts.map +1 -0
- package/dist/core/di.d.ts +18 -0
- package/dist/core/di.d.ts.map +1 -0
- package/dist/core/dialogservice.d.ts +33 -0
- package/dist/core/dialogservice.d.ts.map +1 -0
- package/dist/core/editorregistry.d.ts +73 -0
- package/dist/core/editorregistry.d.ts.map +1 -0
- package/dist/core/esmsh-service.d.ts +40 -0
- package/dist/core/esmsh-service.d.ts.map +1 -0
- package/dist/core/events.d.ts +7 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +63 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/extensionregistry.d.ts +98 -0
- package/dist/core/extensionregistry.d.ts.map +1 -0
- package/dist/core/filesys.d.ts +139 -0
- package/dist/core/filesys.d.ts.map +1 -0
- package/dist/core/i18n.d.ts +50 -0
- package/dist/core/i18n.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/k-utils.d.ts +2 -0
- package/dist/core/k-utils.d.ts.map +1 -0
- package/dist/core/keybindings.d.ts +67 -0
- package/dist/core/keybindings.d.ts.map +1 -0
- package/dist/core/logger.d.ts +44 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/marketplaceregistry.d.ts +25 -0
- package/dist/core/marketplaceregistry.d.ts.map +1 -0
- package/dist/core/packageinfoservice.d.ts +16 -0
- package/dist/core/packageinfoservice.d.ts.map +1 -0
- package/dist/core/persistenceservice.d.ts +6 -0
- package/dist/core/persistenceservice.d.ts.map +1 -0
- package/dist/core/settingsservice.d.ts +19 -0
- package/dist/core/settingsservice.d.ts.map +1 -0
- package/dist/core/signals.d.ts +3 -0
- package/dist/core/signals.d.ts.map +1 -0
- package/dist/core/taskservice.d.ts +20 -0
- package/dist/core/taskservice.d.ts.map +1 -0
- package/dist/core/toast.d.ts +4 -0
- package/dist/core/toast.d.ts.map +1 -0
- package/dist/core/tree-utils.d.ts +16 -0
- package/dist/core/tree-utils.d.ts.map +1 -0
- package/dist/dialogs/confirm-dialog.d.ts +14 -0
- package/dist/dialogs/confirm-dialog.d.ts.map +1 -0
- package/dist/dialogs/index.d.ts +5 -0
- package/dist/dialogs/index.d.ts.map +1 -0
- package/dist/dialogs/info-dialog.d.ts +13 -0
- package/dist/dialogs/info-dialog.d.ts.map +1 -0
- package/dist/dialogs/navigable-info-dialog.d.ts +33 -0
- package/dist/dialogs/navigable-info-dialog.d.ts.map +1 -0
- package/dist/dialogs/prompt-dialog.d.ts +21 -0
- package/dist/dialogs/prompt-dialog.d.ts.map +1 -0
- package/dist/externals/lit.d.ts +20 -0
- package/dist/externals/lit.d.ts.map +1 -0
- package/dist/externals/lit.js +15 -0
- package/dist/externals/lit.js.map +1 -0
- package/dist/externals/third-party.d.ts +7 -0
- package/dist/externals/third-party.d.ts.map +1 -0
- package/dist/externals/third-party.js +2 -0
- package/dist/externals/third-party.js.map +1 -0
- package/dist/externals/webawesome.d.ts +1 -0
- package/dist/externals/webawesome.d.ts.map +1 -0
- package/dist/externals/webawesome.js +52 -0
- package/dist/externals/webawesome.js.map +1 -0
- package/dist/i18n/extensions.json.d.ts +42 -0
- package/dist/i18n/fastviews.json.d.ts +13 -0
- package/dist/i18n/filebrowser.json.d.ts +35 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/logterminal.json.d.ts +45 -0
- package/dist/i18n/partname.json.d.ts +15 -0
- package/dist/i18n/tasks.json.d.ts +15 -0
- package/dist/i18n/workspace.json.d.ts +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/k-icon-BZC7dQV0.js +492 -0
- package/dist/k-icon-BZC7dQV0.js.map +1 -0
- package/dist/k-nocontent-Bh_yToGh.js +48 -0
- package/dist/k-nocontent-Bh_yToGh.js.map +1 -0
- package/dist/k-resizable-grid-Ch3iWZaL.js +3157 -0
- package/dist/k-resizable-grid-Ch3iWZaL.js.map +1 -0
- package/dist/k-standard-layout-CQ1VZoxa.js +5011 -0
- package/dist/k-standard-layout-CQ1VZoxa.js.map +1 -0
- package/dist/layouts/k-standard-layout.d.ts +16 -0
- package/dist/layouts/k-standard-layout.d.ts.map +1 -0
- package/dist/parts/index.d.ts +1 -0
- package/dist/parts/index.d.ts.map +1 -0
- package/dist/parts/index.js +53 -0
- package/dist/parts/index.js.map +1 -0
- package/dist/parts/k-app.d.ts +11 -0
- package/dist/parts/k-app.d.ts.map +1 -0
- package/dist/parts/k-container.d.ts +4 -0
- package/dist/parts/k-container.d.ts.map +1 -0
- package/dist/parts/k-contextmenu.d.ts +38 -0
- package/dist/parts/k-contextmenu.d.ts.map +1 -0
- package/dist/parts/k-dialog-content.d.ts +9 -0
- package/dist/parts/k-dialog-content.d.ts.map +1 -0
- package/dist/parts/k-element.d.ts +36 -0
- package/dist/parts/k-element.d.ts.map +1 -0
- package/dist/parts/k-part.d.ts +96 -0
- package/dist/parts/k-part.d.ts.map +1 -0
- package/dist/parts/k-resizable-grid.d.ts +31 -0
- package/dist/parts/k-resizable-grid.d.ts.map +1 -0
- package/dist/parts/k-tabs.d.ts +74 -0
- package/dist/parts/k-tabs.d.ts.map +1 -0
- package/dist/parts/k-toolbar.d.ts +21 -0
- package/dist/parts/k-toolbar.d.ts.map +1 -0
- package/dist/widgets/index.d.ts +1 -0
- package/dist/widgets/index.d.ts.map +1 -0
- package/dist/widgets/index.js +3 -0
- package/dist/widgets/index.js.map +1 -0
- package/dist/widgets/k-icon.d.ts +10 -0
- package/dist/widgets/k-icon.d.ts.map +1 -0
- package/dist/widgets/k-nocontent.d.ts +13 -0
- package/dist/widgets/k-nocontent.d.ts.map +1 -0
- package/dist/widgets/k-widget.d.ts +25 -0
- package/dist/widgets/k-widget.d.ts.map +1 -0
- package/package.json +81 -0
- package/src/api/base-classes.ts +10 -0
- package/src/api/constants.ts +3 -0
- package/src/api/index.ts +31 -0
- package/src/api/services.ts +52 -0
- package/src/api/types.ts +46 -0
- package/src/commands/files.ts +829 -0
- package/src/commands/global.ts +225 -0
- package/src/commands/index.ts +4 -0
- package/src/commands/version-info.ts +214 -0
- package/src/components/index.ts +10 -0
- package/src/components/k-app-selector.ts +233 -0
- package/src/components/k-app-switcher.ts +126 -0
- package/src/components/k-command.ts +236 -0
- package/src/components/k-extensions.ts +615 -0
- package/src/components/k-fastviews.ts +314 -0
- package/src/components/k-filebrowser.ts +442 -0
- package/src/components/k-language-selector.ts +166 -0
- package/src/components/k-log-terminal.ts +337 -0
- package/src/components/k-part-name.ts +54 -0
- package/src/components/k-tasks.ts +267 -0
- package/src/components/k-workspace-name.ts +56 -0
- package/src/contributions/default-ui-contributions.ts +51 -0
- package/src/contributions/index.ts +3 -0
- package/src/contributions/marketplace-catalog-contributions.ts +6 -0
- package/src/core/app-host-config.ts +23 -0
- package/src/core/apploader.ts +630 -0
- package/src/core/appstate.ts +15 -0
- package/src/core/commandregistry.ts +210 -0
- package/src/core/config.ts +29 -0
- package/src/core/constants.ts +27 -0
- package/src/core/contributionregistry.ts +77 -0
- package/src/core/di.ts +54 -0
- package/src/core/dialogservice.ts +266 -0
- package/src/core/editorregistry.ts +303 -0
- package/src/core/esmsh-service.ts +404 -0
- package/src/core/events.ts +68 -0
- package/src/core/extensionregistry.ts +399 -0
- package/src/core/filesys.ts +618 -0
- package/src/core/i18n.ts +221 -0
- package/src/core/index.ts +51 -0
- package/src/core/k-utils.ts +11 -0
- package/src/core/keybindings.ts +274 -0
- package/src/core/logger.ts +187 -0
- package/src/core/marketplaceregistry.ts +197 -0
- package/src/core/packageinfoservice.ts +56 -0
- package/src/core/persistenceservice.ts +15 -0
- package/src/core/settingsservice.ts +70 -0
- package/src/core/signals.ts +18 -0
- package/src/core/taskservice.ts +72 -0
- package/src/core/toast.ts +11 -0
- package/src/core/tree-utils.ts +24 -0
- package/src/dialogs/confirm-dialog.ts +72 -0
- package/src/dialogs/index.ts +4 -0
- package/src/dialogs/info-dialog.ts +67 -0
- package/src/dialogs/navigable-info-dialog.ts +256 -0
- package/src/dialogs/prompt-dialog.ts +123 -0
- package/src/externals/lit.ts +26 -0
- package/src/externals/third-party.ts +9 -0
- package/src/externals/webawesome.ts +54 -0
- package/src/i18n/extensions.json +39 -0
- package/src/i18n/fastviews.json +10 -0
- package/src/i18n/filebrowser.json +33 -0
- package/src/i18n/index.ts +25 -0
- package/src/i18n/logterminal.json +42 -0
- package/src/i18n/partname.json +12 -0
- package/src/i18n/tasks.json +12 -0
- package/src/i18n/workspace.json +12 -0
- package/src/icons/icons.txt +3 -0
- package/src/icons/js.svg +6 -0
- package/src/icons/jupyter.svg +18 -0
- package/src/icons/python.svg +15 -0
- package/src/index.ts +3 -0
- package/src/layouts/k-standard-layout.ts +174 -0
- package/src/parts/index.ts +6 -0
- package/src/parts/k-app.ts +29 -0
- package/src/parts/k-container.ts +4 -0
- package/src/parts/k-contextmenu.ts +245 -0
- package/src/parts/k-dialog-content.ts +31 -0
- package/src/parts/k-element.ts +100 -0
- package/src/parts/k-part.ts +158 -0
- package/src/parts/k-resizable-grid.ts +366 -0
- package/src/parts/k-tabs.ts +574 -0
- package/src/parts/k-toolbar.ts +158 -0
- package/src/vite-env.d.ts +2 -0
- package/src/widgets/index.ts +2 -0
- package/src/widgets/k-icon.ts +39 -0
- package/src/widgets/k-nocontent.ts +40 -0
- package/src/widgets/k-widget.ts +90 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import {customElement, state} from "lit/decorators.js";
|
|
2
|
+
import {css, html, nothing} from "lit";
|
|
3
|
+
import {KContainer} from "./k-container";
|
|
4
|
+
import {contributionRegistry, ContributionChangeEvent, TabContribution, TOPIC_CONTRIBUTEIONS_CHANGED} from "../core/contributionregistry";
|
|
5
|
+
import {when} from "lit/directives/when.js";
|
|
6
|
+
import {repeat} from "lit/directives/repeat.js";
|
|
7
|
+
import '../widgets/k-icon';
|
|
8
|
+
import {createRef, ref} from "lit/directives/ref.js";
|
|
9
|
+
import {subscribe} from "../core/events";
|
|
10
|
+
import {KPart} from "./k-part";
|
|
11
|
+
import {KToolbar} from "./k-toolbar";
|
|
12
|
+
import {KContextMenu} from "./k-contextmenu";
|
|
13
|
+
import {MouseButton, EDITOR_AREA_MAIN} from "../core/constants";
|
|
14
|
+
import {activePartSignal} from "../core/appstate";
|
|
15
|
+
import {confirmDialog} from "../dialogs";
|
|
16
|
+
import {appLoaderService} from "../core/apploader";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* KTabs - A dynamic tab container component
|
|
20
|
+
*
|
|
21
|
+
* Architecture:
|
|
22
|
+
* - Fixed layout (VS Code style) - each tab is registered to a specific container
|
|
23
|
+
* - Tabs are created/destroyed as needed (no instance reuse)
|
|
24
|
+
* - Support for both static (views) and dynamic (editors) tabs
|
|
25
|
+
*
|
|
26
|
+
* Lifecycle:
|
|
27
|
+
* 1. doInitUI(): Load contributions, activate first tab
|
|
28
|
+
* 2. render(): Create tab UI from contributions
|
|
29
|
+
* 3. open/closeTab(): Dynamic tab operations
|
|
30
|
+
*/
|
|
31
|
+
@customElement('k-tabs')
|
|
32
|
+
export class KTabs extends KContainer {
|
|
33
|
+
/** Tab contributions for this container */
|
|
34
|
+
@state()
|
|
35
|
+
private contributions: TabContribution[] = [];
|
|
36
|
+
|
|
37
|
+
/** Reference to the underlying wa-tab-group element */
|
|
38
|
+
private tabGroup = createRef()
|
|
39
|
+
|
|
40
|
+
/** Cached container ID (this element's 'id' attribute) */
|
|
41
|
+
private containerId: string | null = null;
|
|
42
|
+
|
|
43
|
+
/** Map to track ResizeObservers for cleanup */
|
|
44
|
+
private resizeObservers = new WeakMap<HTMLElement, ResizeObserver>();
|
|
45
|
+
|
|
46
|
+
// ============= Lifecycle Methods =============
|
|
47
|
+
|
|
48
|
+
protected doBeforeUI() {
|
|
49
|
+
this.containerId = this.getAttribute("id");
|
|
50
|
+
if (!this.containerId) {
|
|
51
|
+
throw new Error("k-tabs requires an 'id' attribute to function");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.loadAndResolveContributions();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected doInitUI() {
|
|
58
|
+
this.updateComplete.then(() => {
|
|
59
|
+
this.activateNextAvailableTab();
|
|
60
|
+
|
|
61
|
+
if (!this.tabGroup.value) return;
|
|
62
|
+
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
this.tabGroup.value.addEventListener("wa-tab-show", (event: CustomEvent) => {
|
|
65
|
+
const tabPanel = this.getTabPanel(event.detail.name);
|
|
66
|
+
if (tabPanel) {
|
|
67
|
+
// Update toolbar from component's renderToolbar() method
|
|
68
|
+
this.updateToolbarFromComponent(tabPanel);
|
|
69
|
+
// Update toolbar height variable for calc() positioning
|
|
70
|
+
requestAnimationFrame(() => {
|
|
71
|
+
this.updateToolbarHeightVariable(tabPanel);
|
|
72
|
+
this.setupToolbarResizeObserver(tabPanel);
|
|
73
|
+
});
|
|
74
|
+
this.dispatchEvent(new CustomEvent('tab-shown', {detail: tabPanel}));
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Listen for toolbar update requests from components
|
|
79
|
+
this.tabGroup.value.addEventListener("part-toolbar-changed", (event: Event) => {
|
|
80
|
+
const component = event.target as HTMLElement;
|
|
81
|
+
const tabPanel = component.closest('wa-tab-panel') as HTMLElement | null;
|
|
82
|
+
if (tabPanel) {
|
|
83
|
+
this.updateToolbarFromComponent(tabPanel);
|
|
84
|
+
// Update toolbar height variable for calc() positioning
|
|
85
|
+
requestAnimationFrame(() => this.updateToolbarHeightVariable(tabPanel));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Listen for context menu update requests from components
|
|
90
|
+
this.tabGroup.value.addEventListener("part-contextmenu-changed", (event: Event) => {
|
|
91
|
+
const component = event.target as HTMLElement;
|
|
92
|
+
const tabPanel = component.closest('wa-tab-panel') as HTMLElement | null;
|
|
93
|
+
if (tabPanel) {
|
|
94
|
+
this.updateContextMenuFromComponent(tabPanel);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Update active part signal when clicking anywhere in tab content or tab title
|
|
99
|
+
this.tabGroup.value.addEventListener('click', (event: Event) => {
|
|
100
|
+
const target = event.target as HTMLElement;
|
|
101
|
+
|
|
102
|
+
// Handle clicks on tab titles
|
|
103
|
+
const tab = target.closest('wa-tab');
|
|
104
|
+
if (tab) {
|
|
105
|
+
const panelName = tab.getAttribute('panel');
|
|
106
|
+
if (panelName) {
|
|
107
|
+
const tabPanel = this.getTabPanel(panelName);
|
|
108
|
+
if (tabPanel) {
|
|
109
|
+
const contentDiv = tabPanel.querySelector('.tab-content');
|
|
110
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
111
|
+
const part = contentDiv.firstElementChild;
|
|
112
|
+
if (part instanceof KPart) {
|
|
113
|
+
activePartSignal.set(part);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Handle clicks on tab content
|
|
122
|
+
const scroller = target.closest('wa-scroller.tab-content');
|
|
123
|
+
if (!scroller) return;
|
|
124
|
+
|
|
125
|
+
const tabPanel = scroller.closest('wa-tab-panel') as HTMLElement;
|
|
126
|
+
if (!tabPanel) return;
|
|
127
|
+
|
|
128
|
+
const contentDiv = tabPanel.querySelector('.tab-content');
|
|
129
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
130
|
+
const part = contentDiv.firstElementChild;
|
|
131
|
+
if (part instanceof KPart) {
|
|
132
|
+
activePartSignal.set(part);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Automatically wire up context menus for all tab content
|
|
138
|
+
this.tabGroup.value.addEventListener('contextmenu', (event: Event) => {
|
|
139
|
+
const mouseEvent = event as MouseEvent;
|
|
140
|
+
const scroller = (mouseEvent.target as HTMLElement).closest('wa-scroller.tab-content');
|
|
141
|
+
if (!scroller) return;
|
|
142
|
+
|
|
143
|
+
mouseEvent.preventDefault();
|
|
144
|
+
|
|
145
|
+
const tabPanel = scroller.closest('wa-tab-panel') as HTMLElement;
|
|
146
|
+
if (!tabPanel) return;
|
|
147
|
+
|
|
148
|
+
// Wait for selection to update before showing context menu
|
|
149
|
+
requestAnimationFrame(() => {
|
|
150
|
+
this.updateContextMenuFromComponent(tabPanel);
|
|
151
|
+
|
|
152
|
+
const contextMenu = tabPanel.querySelector('k-contextmenu') as KContextMenu;
|
|
153
|
+
if (contextMenu) {
|
|
154
|
+
contextMenu.show({ x: mouseEvent.clientX, y: mouseEvent.clientY }, mouseEvent);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event: ContributionChangeEvent) => {
|
|
161
|
+
if (!this.containerId || event.target !== this.containerId) return;
|
|
162
|
+
|
|
163
|
+
this.loadAndResolveContributions();
|
|
164
|
+
this.requestUpdate();
|
|
165
|
+
|
|
166
|
+
this.updateComplete.then(() => {
|
|
167
|
+
this.activateNextAvailableTab();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
updated(changedProperties: Map<string, any>) {
|
|
173
|
+
super.updated(changedProperties);
|
|
174
|
+
|
|
175
|
+
if (changedProperties.has('contributions')) {
|
|
176
|
+
const isEditorArea = this.containerId === EDITOR_AREA_MAIN;
|
|
177
|
+
this.contributions.forEach(contribution => {
|
|
178
|
+
const tabPanel = this.getTabPanel(contribution.name);
|
|
179
|
+
if (!tabPanel) return;
|
|
180
|
+
|
|
181
|
+
const contentDiv = tabPanel.querySelector('.tab-content');
|
|
182
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
183
|
+
const part = contentDiv.firstElementChild;
|
|
184
|
+
if (part instanceof KPart) {
|
|
185
|
+
part.tabContribution = contribution;
|
|
186
|
+
part.isEditor = isEditorArea;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
requestAnimationFrame(() => this.updateToolbarHeightVariable(tabPanel));
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ============= Public API Methods =============
|
|
196
|
+
|
|
197
|
+
has(key: string): boolean {
|
|
198
|
+
if (!this.tabGroup.value) return false;
|
|
199
|
+
return !!this.getTabPanel(key);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
activate(key: string): void {
|
|
203
|
+
if (!this.tabGroup.value) return;
|
|
204
|
+
this.tabGroup.value.setAttribute("active", key);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public getActiveEditor(): string | null {
|
|
208
|
+
if (!this.tabGroup.value) return null;
|
|
209
|
+
return this.tabGroup.value.getAttribute("active");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
open(contribution: TabContribution): void {
|
|
213
|
+
// Check if contribution already exists, if so just activate it
|
|
214
|
+
const existing = this.contributions.find(c => c.name === contribution.name);
|
|
215
|
+
if (existing) {
|
|
216
|
+
this.activate(contribution.name);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.contributions.push(contribution);
|
|
221
|
+
this.requestUpdate();
|
|
222
|
+
|
|
223
|
+
this.updateComplete.then(() => {
|
|
224
|
+
this.activate(contribution.name);
|
|
225
|
+
// Update toolbar after component is rendered
|
|
226
|
+
const tabPanel = this.getTabPanel(contribution.name);
|
|
227
|
+
if (tabPanel) {
|
|
228
|
+
const contentDiv = tabPanel.querySelector('.tab-content');
|
|
229
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
230
|
+
const part = contentDiv.firstElementChild;
|
|
231
|
+
if (part instanceof KPart) {
|
|
232
|
+
part.tabContribution = contribution;
|
|
233
|
+
part.isEditor = this.containerId === EDITOR_AREA_MAIN;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Give component time to initialize
|
|
238
|
+
requestAnimationFrame(() => {
|
|
239
|
+
this.updateToolbarFromComponent(tabPanel);
|
|
240
|
+
this.updateToolbarHeightVariable(tabPanel);
|
|
241
|
+
this.setupToolbarResizeObserver(tabPanel);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
handleTabAuxClick(event: MouseEvent, contribution: TabContribution): void {
|
|
248
|
+
if (event.button === MouseButton.MIDDLE && contribution.closable) {
|
|
249
|
+
this.closeTab(event, contribution.name);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async closeTab(event: Event, tabName: string): Promise<void> {
|
|
254
|
+
event.stopPropagation();
|
|
255
|
+
|
|
256
|
+
if (this.isDirty(tabName) && !await confirmDialog("Unsaved changes will be lost: Do you really want to close?")) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const tabPanel = this.getTabPanel(tabName);
|
|
261
|
+
if (!tabPanel) return;
|
|
262
|
+
|
|
263
|
+
const contribution = this.contributions.find(c => c.name === tabName);
|
|
264
|
+
if (!contribution) return;
|
|
265
|
+
|
|
266
|
+
this.cleanupTabInstance(tabPanel);
|
|
267
|
+
|
|
268
|
+
const index = this.contributions.indexOf(contribution);
|
|
269
|
+
if (index > -1) {
|
|
270
|
+
this.contributions.splice(index, 1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.dispatchEvent(new CustomEvent('tab-closed', {detail: tabPanel}));
|
|
274
|
+
|
|
275
|
+
this.requestUpdate();
|
|
276
|
+
|
|
277
|
+
this.updateComplete.then(() => {
|
|
278
|
+
this.activateNextAvailableTab();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
markDirty(name: string, dirty: boolean): void {
|
|
283
|
+
const tab = this.getTab(name);
|
|
284
|
+
tab!.classList.toggle("part-dirty", dirty);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
isDirty(name: string): boolean {
|
|
288
|
+
const tab = this.getTab(name);
|
|
289
|
+
return tab!.classList.contains("part-dirty");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ============= Private Helper Methods =============
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Loads tab contributions from the registry.
|
|
296
|
+
*/
|
|
297
|
+
private loadAndResolveContributions(): void {
|
|
298
|
+
this.contributions = contributionRegistry.getContributions(this.containerId!) as TabContribution[];
|
|
299
|
+
this.requestUpdate();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Cleans up a tab instance when the tab is closed.
|
|
304
|
+
*
|
|
305
|
+
* Cleanup Process:
|
|
306
|
+
* 1. Disconnect ResizeObserver if one exists
|
|
307
|
+
* 2. Call component's close() method if available (disposes resources)
|
|
308
|
+
* 3. DOM element is removed by caller (closeTab method)
|
|
309
|
+
*/
|
|
310
|
+
private cleanupTabInstance(tabPanel: HTMLElement): void {
|
|
311
|
+
// Clean up ResizeObserver
|
|
312
|
+
const observer = this.resizeObservers.get(tabPanel);
|
|
313
|
+
if (observer) {
|
|
314
|
+
observer.disconnect();
|
|
315
|
+
this.resizeObservers.delete(tabPanel);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Explicitly close the component inside the tab before removing
|
|
319
|
+
// This allows components to dispose resources (e.g., Monaco editor models, event listeners)
|
|
320
|
+
const contentDiv = tabPanel.querySelector('.tab-content');
|
|
321
|
+
if (contentDiv && contentDiv.firstElementChild) {
|
|
322
|
+
const component = contentDiv.firstElementChild;
|
|
323
|
+
if ('close' in component && typeof component.close === 'function') {
|
|
324
|
+
component.close();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private activateNextAvailableTab(): void {
|
|
330
|
+
// Guard: Component might not be fully initialized yet
|
|
331
|
+
if (!this.tabGroup.value) return;
|
|
332
|
+
|
|
333
|
+
const allRemainingTabs = this.tabGroup.value.querySelectorAll("wa-tab");
|
|
334
|
+
if (allRemainingTabs.length > 0) {
|
|
335
|
+
const newActive = allRemainingTabs.item(0).getAttribute("panel");
|
|
336
|
+
if (newActive) {
|
|
337
|
+
this.tabGroup.value.setAttribute("active", newActive);
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
this.tabGroup.value.removeAttribute("active");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private getTabPanel(name: string): HTMLElement | null {
|
|
345
|
+
if (!this.tabGroup.value) return null;
|
|
346
|
+
return this.tabGroup.value.querySelector(`wa-tab-panel[name='${name}']`) as HTMLElement | null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private getTab(name: string): HTMLElement | null {
|
|
350
|
+
if (!this.tabGroup.value) return null;
|
|
351
|
+
return this.tabGroup.value.querySelector(`wa-tab[panel='${name}']`) as HTMLElement | null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Updates the toolbar for a tab panel by querying the component for its toolbar content.
|
|
356
|
+
* This allows KPart components to provide their own toolbar items directly.
|
|
357
|
+
*/
|
|
358
|
+
private updateToolbarFromComponent(tabPanel: HTMLElement): void {
|
|
359
|
+
const contentDiv = tabPanel.querySelector('.tab-content');
|
|
360
|
+
if (!contentDiv || !contentDiv.firstElementChild) return;
|
|
361
|
+
|
|
362
|
+
const component = contentDiv.firstElementChild;
|
|
363
|
+
if (!(component instanceof KPart)) return;
|
|
364
|
+
|
|
365
|
+
// Check if component has renderToolbar method
|
|
366
|
+
if (!component['renderToolbar']) return;
|
|
367
|
+
|
|
368
|
+
// Query for k-toolbar directly since there's only one per tab panel
|
|
369
|
+
const toolbar = tabPanel.querySelector('k-toolbar') as KToolbar | null;
|
|
370
|
+
if (toolbar) {
|
|
371
|
+
// Pass a bound render function to maintain component context
|
|
372
|
+
toolbar.partToolbarRenderer = () => component['renderToolbar']();
|
|
373
|
+
toolbar.requestUpdate();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Updates the context menu for a tab panel by querying the component for its context menu content.
|
|
379
|
+
* This allows KPart components to provide their own context menu items directly.
|
|
380
|
+
*/
|
|
381
|
+
private updateContextMenuFromComponent(tabPanel: HTMLElement): void {
|
|
382
|
+
const contentDiv = tabPanel.querySelector('.tab-content');
|
|
383
|
+
if (!contentDiv || !contentDiv.firstElementChild) return;
|
|
384
|
+
|
|
385
|
+
const component = contentDiv.firstElementChild;
|
|
386
|
+
if (!(component instanceof KPart)) return;
|
|
387
|
+
|
|
388
|
+
// Check if component has renderContextMenu method
|
|
389
|
+
if (!component['renderContextMenu']) return;
|
|
390
|
+
|
|
391
|
+
// Query for k-contextmenu directly since there's only one per tab panel
|
|
392
|
+
const contextMenu = tabPanel.querySelector('k-contextmenu') as KContextMenu | null;
|
|
393
|
+
if (contextMenu) {
|
|
394
|
+
// Pass a bound render function to maintain component context
|
|
395
|
+
contextMenu.partContextMenuRenderer = () => component['renderContextMenu']();
|
|
396
|
+
contextMenu.requestUpdate();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Updates the toolbar height CSS variable for calc() positioning.
|
|
402
|
+
*/
|
|
403
|
+
private updateToolbarHeightVariable(tabPanel: HTMLElement): void {
|
|
404
|
+
const toolbar = tabPanel.querySelector('.tab-toolbar') as HTMLElement | null;
|
|
405
|
+
if (!toolbar) return;
|
|
406
|
+
|
|
407
|
+
const toolbarHeight = toolbar.offsetHeight;
|
|
408
|
+
tabPanel.style.setProperty('--toolbar-height', `${toolbarHeight}px`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Sets up a ResizeObserver to update toolbar height variable when toolbar size changes.
|
|
413
|
+
* Reuses existing observer if one already exists for this tab panel.
|
|
414
|
+
*/
|
|
415
|
+
private setupToolbarResizeObserver(tabPanel: HTMLElement): void {
|
|
416
|
+
// Check if observer already exists
|
|
417
|
+
if (this.resizeObservers.has(tabPanel)) return;
|
|
418
|
+
|
|
419
|
+
const toolbar = tabPanel.querySelector('.tab-toolbar') as HTMLElement | null;
|
|
420
|
+
if (!toolbar) return;
|
|
421
|
+
|
|
422
|
+
const observer = new ResizeObserver(() => {
|
|
423
|
+
this.updateToolbarHeightVariable(tabPanel);
|
|
424
|
+
});
|
|
425
|
+
observer.observe(toolbar);
|
|
426
|
+
this.resizeObservers.set(tabPanel, observer);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ============= Render Method =============
|
|
430
|
+
|
|
431
|
+
render() {
|
|
432
|
+
const currentApp = appLoaderService.getCurrentApp();
|
|
433
|
+
|
|
434
|
+
return html`
|
|
435
|
+
<wa-tab-group ${ref(this.tabGroup)}>
|
|
436
|
+
${when(
|
|
437
|
+
this.contributions.length === 0,
|
|
438
|
+
() => html`
|
|
439
|
+
<div class="empty-state">
|
|
440
|
+
${when(
|
|
441
|
+
currentApp,
|
|
442
|
+
() => html`
|
|
443
|
+
<div class="empty-content">
|
|
444
|
+
<h2 class="empty-title">${currentApp!.name}</h2>
|
|
445
|
+
${when(
|
|
446
|
+
currentApp!.description,
|
|
447
|
+
() => html`<p class="empty-description">${currentApp!.description}</p>`
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
450
|
+
`,
|
|
451
|
+
() => html`
|
|
452
|
+
<wa-icon name="folder-open" class="empty-icon"></wa-icon>
|
|
453
|
+
`
|
|
454
|
+
)}
|
|
455
|
+
</div>
|
|
456
|
+
`,
|
|
457
|
+
() => repeat(
|
|
458
|
+
this.contributions,
|
|
459
|
+
(c) => c.name,
|
|
460
|
+
(c) => html`
|
|
461
|
+
<wa-tab panel="${c.name}"
|
|
462
|
+
@auxclick="${(e: MouseEvent) => this.handleTabAuxClick(e, c)}">
|
|
463
|
+
<k-icon name="${c.icon!}"></k-icon>
|
|
464
|
+
${c.label}
|
|
465
|
+
${when(c.closable, () => html`
|
|
466
|
+
<wa-icon name="xmark" label="Close" @click="${(e: Event) => this.closeTab(e, c.name)}"></wa-icon>
|
|
467
|
+
`)}
|
|
468
|
+
</wa-tab>
|
|
469
|
+
<wa-tab-panel name="${c.name}">
|
|
470
|
+
<k-toolbar id="toolbar:${c.editorId ?? c.name}"
|
|
471
|
+
class="tab-toolbar"
|
|
472
|
+
?is-editor="${this.containerId === EDITOR_AREA_MAIN}"></k-toolbar>
|
|
473
|
+
<wa-scroller class="tab-content" orientation="vertical">
|
|
474
|
+
${c.component ? c.component(c.name) : nothing}
|
|
475
|
+
</wa-scroller>
|
|
476
|
+
<k-contextmenu id="contextmenu:${c.name}"
|
|
477
|
+
?is-editor="${this.containerId === EDITOR_AREA_MAIN}"></k-contextmenu>
|
|
478
|
+
</wa-tab-panel>
|
|
479
|
+
`
|
|
480
|
+
)
|
|
481
|
+
)}
|
|
482
|
+
</wa-tab-group>
|
|
483
|
+
`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
static styles = css`
|
|
487
|
+
:host {
|
|
488
|
+
height: 100%;
|
|
489
|
+
width: 100%;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
wa-tab-group {
|
|
493
|
+
height: 100%;
|
|
494
|
+
width: 100%;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
wa-tab-group::part(base) {
|
|
498
|
+
display: grid;
|
|
499
|
+
grid-template-rows: auto minmax(0, 1fr);
|
|
500
|
+
height: 100%;
|
|
501
|
+
width: 100%;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
wa-tab-panel[active] {
|
|
505
|
+
display: grid;
|
|
506
|
+
grid-template-rows: minmax(0, 1fr);
|
|
507
|
+
height: 100%;
|
|
508
|
+
width: 100%;
|
|
509
|
+
overflow: hidden;
|
|
510
|
+
position: relative;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.tab-content {
|
|
514
|
+
position: absolute;
|
|
515
|
+
top: calc(var(--toolbar-height, 0px));
|
|
516
|
+
right: 0;
|
|
517
|
+
left: 0;
|
|
518
|
+
height: calc(100% - var(--toolbar-height, 0px));
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
wa-tab::part(base) {
|
|
522
|
+
padding: 3px 0.5rem;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
wa-tab-panel {
|
|
526
|
+
--padding: 0px;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.part-dirty::part(base) {
|
|
530
|
+
font-style: italic;
|
|
531
|
+
color: var(--wa-color-danger-fill-loud)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.empty-state {
|
|
535
|
+
display: flex;
|
|
536
|
+
align-items: center;
|
|
537
|
+
justify-content: center;
|
|
538
|
+
width: 100%;
|
|
539
|
+
height: 100%;
|
|
540
|
+
grid-row: 2;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.empty-content {
|
|
544
|
+
display: flex;
|
|
545
|
+
flex-direction: column;
|
|
546
|
+
align-items: center;
|
|
547
|
+
justify-content: center;
|
|
548
|
+
text-align: center;
|
|
549
|
+
padding: 2rem;
|
|
550
|
+
gap: 0.75rem;
|
|
551
|
+
opacity: 0.3;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.empty-title {
|
|
555
|
+
margin: 0;
|
|
556
|
+
font-size: 1.5rem;
|
|
557
|
+
font-weight: 500;
|
|
558
|
+
color: var(--wa-color-text-quiet);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.empty-description {
|
|
562
|
+
margin: 0;
|
|
563
|
+
font-size: 1rem;
|
|
564
|
+
color: var(--wa-color-text-quiet);
|
|
565
|
+
max-width: 500px;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.empty-icon {
|
|
569
|
+
font-size: 6rem;
|
|
570
|
+
opacity: 0.2;
|
|
571
|
+
color: var(--wa-color-text-quiet);
|
|
572
|
+
}
|
|
573
|
+
`
|
|
574
|
+
}
|