@matdata/yasgui 4.6.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 +175 -0
- package/build/ts/src/PersistentConfig.d.ts +49 -0
- package/build/ts/src/Tab.d.ts +105 -0
- package/build/ts/src/TabContextMenu.d.ts +29 -0
- package/build/ts/src/TabElements.d.ts +45 -0
- package/build/ts/src/TabSettingsModal.d.ts +28 -0
- package/build/ts/src/defaults.d.ts +3 -0
- package/build/ts/src/endpointSelect.d.ts +44 -0
- package/build/ts/src/index.d.ts +104 -0
- package/build/ts/src/linkUtils.d.ts +43 -0
- package/build/yasgui.html +24 -0
- package/build/yasgui.min.css +2 -0
- package/build/yasgui.min.css.map +1 -0
- package/build/yasgui.min.js +3 -0
- package/build/yasgui.min.js.LICENSE.txt +59 -0
- package/build/yasgui.min.js.map +1 -0
- package/package.json +48 -0
- package/src/PersistentConfig.ts +159 -0
- package/src/Tab.ts +775 -0
- package/src/TabContextMenu.scss +42 -0
- package/src/TabContextMenu.ts +143 -0
- package/src/TabElements.scss +134 -0
- package/src/TabElements.ts +336 -0
- package/src/TabSettingsModal.scss +226 -0
- package/src/TabSettingsModal.ts +424 -0
- package/src/defaults.ts +64 -0
- package/src/endpointSelect.scss +122 -0
- package/src/endpointSelect.ts +339 -0
- package/src/index.scss +97 -0
- package/src/index.ts +373 -0
- package/src/linkUtils.ts +234 -0
- package/src/tab.scss +61 -0
- package/static/yasgui.bootstrap.css +36 -0
- package/static/yasgui.polyfill.min.js +4 -0
- package/typings-custom/@tarekraafat/autocomplete.js/index.d.ts +48 -0
- package/typings-custom/main.d.ts +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
.yasgui.context-menu {
|
|
2
|
+
position: absolute;
|
|
3
|
+
z-index: 10;
|
|
4
|
+
background: white;
|
|
5
|
+
min-width: 160px;
|
|
6
|
+
font-size: 14px;
|
|
7
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
8
|
+
border-bottom-right-radius: 4px;
|
|
9
|
+
border-bottom-left-radius: 4px;
|
|
10
|
+
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
|
11
|
+
background-clip: padding-box;
|
|
12
|
+
hr {
|
|
13
|
+
margin: 8px auto;
|
|
14
|
+
border: none;
|
|
15
|
+
border-top: 1px solid #aaaaaa;
|
|
16
|
+
border-bottom: 1px solid #ffffff;
|
|
17
|
+
}
|
|
18
|
+
.context-menu-list {
|
|
19
|
+
padding: 0;
|
|
20
|
+
}
|
|
21
|
+
.context-menu-item {
|
|
22
|
+
display: block;
|
|
23
|
+
padding: 3px 20px;
|
|
24
|
+
clear: both;
|
|
25
|
+
font-weight: 400;
|
|
26
|
+
line-height: 1.42857;
|
|
27
|
+
color: #333;
|
|
28
|
+
white-space: nowrap;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
&:hover {
|
|
31
|
+
text-decoration: none;
|
|
32
|
+
color: black;
|
|
33
|
+
background-color: #f5f5f5;
|
|
34
|
+
}
|
|
35
|
+
&.disabled {
|
|
36
|
+
text-decoration: none;
|
|
37
|
+
color: gray;
|
|
38
|
+
background-color: #e5e5e5;
|
|
39
|
+
cursor: not-allowed;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { addClass } from "@matdata/yasgui-utils";
|
|
2
|
+
import { default as Yasgui, getRandomId } from "./";
|
|
3
|
+
import Tab from "./Tab";
|
|
4
|
+
import { TabListEl } from "./TabElements";
|
|
5
|
+
import { cloneDeep } from "lodash-es";
|
|
6
|
+
require("./TabContextMenu.scss");
|
|
7
|
+
export interface TabContextConfig {
|
|
8
|
+
name: string;
|
|
9
|
+
action: (this: HTMLElement, ev: MouseEvent) => any;
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
}
|
|
12
|
+
export default class TabContextMenu {
|
|
13
|
+
private yasgui: Yasgui;
|
|
14
|
+
private contextEl!: HTMLElement;
|
|
15
|
+
private newTabEl!: HTMLElement;
|
|
16
|
+
private renameTabEl!: HTMLElement;
|
|
17
|
+
private copyTabEl!: HTMLElement;
|
|
18
|
+
private closeTabEl!: HTMLElement;
|
|
19
|
+
private closeOtherTabsEl!: HTMLElement;
|
|
20
|
+
private reOpenOldTab!: HTMLElement;
|
|
21
|
+
private rootEl: HTMLElement;
|
|
22
|
+
private tabRef: TabListEl | undefined; // Need to store it due to scrolling updates
|
|
23
|
+
constructor(yasgui: Yasgui, rootEl: HTMLElement) {
|
|
24
|
+
this.yasgui = yasgui;
|
|
25
|
+
this.rootEl = rootEl;
|
|
26
|
+
document.addEventListener("click", this.handleContextClick);
|
|
27
|
+
document.addEventListener("keyup", this.closeConfigMenu);
|
|
28
|
+
}
|
|
29
|
+
private getMenuItemEl(text?: string) {
|
|
30
|
+
const item = document.createElement("li");
|
|
31
|
+
addClass(item, "context-menu-item");
|
|
32
|
+
// Make sure hitting 'rmb' multiple times doesn't close the menu
|
|
33
|
+
item.addEventListener("contextmenu", (event) => {
|
|
34
|
+
event.stopPropagation();
|
|
35
|
+
});
|
|
36
|
+
if (text !== undefined) item.innerText = text;
|
|
37
|
+
return item;
|
|
38
|
+
}
|
|
39
|
+
private draw(rootEl: HTMLElement) {
|
|
40
|
+
this.contextEl = document.createElement("div");
|
|
41
|
+
const dropDownList = document.createElement("ul");
|
|
42
|
+
addClass(dropDownList, "context-menu-list");
|
|
43
|
+
|
|
44
|
+
this.newTabEl = this.getMenuItemEl("New Tab");
|
|
45
|
+
// We can set the function for addTab here already, as it doesn't need any outside data
|
|
46
|
+
this.newTabEl.onclick = () => this.yasgui.addTab(true);
|
|
47
|
+
|
|
48
|
+
this.renameTabEl = this.getMenuItemEl("Rename Tab");
|
|
49
|
+
|
|
50
|
+
this.copyTabEl = this.getMenuItemEl("Copy Tab");
|
|
51
|
+
|
|
52
|
+
this.closeTabEl = this.getMenuItemEl("Close Tab");
|
|
53
|
+
|
|
54
|
+
this.closeOtherTabsEl = this.getMenuItemEl("Close other tabs");
|
|
55
|
+
|
|
56
|
+
this.reOpenOldTab = this.getMenuItemEl("Undo close Tab");
|
|
57
|
+
|
|
58
|
+
// Add items to list
|
|
59
|
+
dropDownList.appendChild(this.newTabEl);
|
|
60
|
+
dropDownList.appendChild(this.renameTabEl);
|
|
61
|
+
dropDownList.appendChild(this.copyTabEl);
|
|
62
|
+
// Add divider
|
|
63
|
+
dropDownList.appendChild(document.createElement("hr"));
|
|
64
|
+
dropDownList.appendChild(this.closeTabEl);
|
|
65
|
+
dropDownList.appendChild(this.closeOtherTabsEl);
|
|
66
|
+
dropDownList.appendChild(this.reOpenOldTab);
|
|
67
|
+
this.contextEl.appendChild(dropDownList);
|
|
68
|
+
addClass(this.contextEl, "yasgui", "context-menu");
|
|
69
|
+
rootEl.appendChild(this.contextEl);
|
|
70
|
+
}
|
|
71
|
+
public redraw() {
|
|
72
|
+
if (this.contextEl && this.tabRef?.tabEl) {
|
|
73
|
+
const bounding = this.tabRef.tabEl.getBoundingClientRect();
|
|
74
|
+
this.contextEl.style.top = `${window.pageYOffset + bounding.bottom}px`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
handleContextClick = (event: MouseEvent) => {
|
|
78
|
+
if (event.button !== 2) {
|
|
79
|
+
this.closeConfigMenu();
|
|
80
|
+
} else if (event.target !== this.contextEl) {
|
|
81
|
+
this.closeConfigMenu();
|
|
82
|
+
} else {
|
|
83
|
+
event.stopImmediatePropagation();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
public openConfigMenu(currentTabId: string, currentTabEl: TabListEl, event: MouseEvent) {
|
|
88
|
+
if (!currentTabEl.tabEl) return;
|
|
89
|
+
this.draw(this.rootEl);
|
|
90
|
+
this.tabRef = currentTabEl;
|
|
91
|
+
const tab = this.yasgui.getTab(currentTabId);
|
|
92
|
+
const bounding = currentTabEl.tabEl.getBoundingClientRect();
|
|
93
|
+
this.contextEl.style.left = `${window.pageXOffset + bounding.left}px`;
|
|
94
|
+
this.contextEl.style.top = `${window.pageYOffset + bounding.bottom}px`;
|
|
95
|
+
event.stopPropagation();
|
|
96
|
+
|
|
97
|
+
// Set rename functionality
|
|
98
|
+
this.renameTabEl.onclick = () => currentTabEl.startRename();
|
|
99
|
+
|
|
100
|
+
// Copy tab functionality`
|
|
101
|
+
this.copyTabEl.onclick = () => {
|
|
102
|
+
if (!tab) return;
|
|
103
|
+
const config = cloneDeep(tab.getPersistedJson());
|
|
104
|
+
config.id = getRandomId();
|
|
105
|
+
this.yasgui.addTab(true, config);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Close tab functionality
|
|
109
|
+
this.closeTabEl.onclick = () => tab?.close();
|
|
110
|
+
|
|
111
|
+
// Close other tab functionality
|
|
112
|
+
if (Object.keys(this.yasgui._tabs).length === 1) {
|
|
113
|
+
addClass(this.closeOtherTabsEl, "disabled");
|
|
114
|
+
} else {
|
|
115
|
+
this.closeOtherTabsEl.onclick = () => {
|
|
116
|
+
for (const tabId of Object.keys(this.yasgui._tabs)) {
|
|
117
|
+
if (tabId !== currentTabId) (this.yasgui.getTab(tabId) as Tab).close();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (this.yasgui.persistentConfig && this.yasgui.persistentConfig.hasLastClosedTab()) {
|
|
122
|
+
this.reOpenOldTab.onclick = () => this.yasgui.restoreLastTab();
|
|
123
|
+
} else {
|
|
124
|
+
addClass(this.reOpenOldTab, "disabled");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
public closeConfigMenu = () => {
|
|
128
|
+
this.tabRef = undefined;
|
|
129
|
+
if (this.contextEl) this.contextEl.remove();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
public static get(yasgui: Yasgui, rootEl: HTMLElement) {
|
|
133
|
+
const instance = new TabContextMenu(yasgui, rootEl);
|
|
134
|
+
return instance;
|
|
135
|
+
}
|
|
136
|
+
public unregisterEventListeners() {
|
|
137
|
+
document.removeEventListener("click", this.handleContextClick);
|
|
138
|
+
document.removeEventListener("keyup", this.closeConfigMenu);
|
|
139
|
+
}
|
|
140
|
+
public destroy() {
|
|
141
|
+
this.unregisterEventListeners();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
@use "sass:color";
|
|
2
|
+
$minTabHeight: 35px;
|
|
3
|
+
.yasgui {
|
|
4
|
+
.tabsList {
|
|
5
|
+
.sortable-placeholder {
|
|
6
|
+
min-width: 100px;
|
|
7
|
+
min-height: $minTabHeight;
|
|
8
|
+
border: 2px dotted color.adjust(#555, $lightness: 20%);
|
|
9
|
+
}
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-wrap: wrap;
|
|
12
|
+
a {
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
min-height: $minTabHeight;
|
|
18
|
+
border-bottom: 2px solid transparent;
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
.addTab {
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
height: 100%;
|
|
24
|
+
font-size: 120%;
|
|
25
|
+
font-weight: 800;
|
|
26
|
+
margin-left: 15px;
|
|
27
|
+
padding: 0px 5px 2px 5px;
|
|
28
|
+
background: inherit;
|
|
29
|
+
border: none;
|
|
30
|
+
color: #337ab7;
|
|
31
|
+
|
|
32
|
+
&:hover,
|
|
33
|
+
&:focus-visible {
|
|
34
|
+
transform: scale(1.1);
|
|
35
|
+
}
|
|
36
|
+
&:focus {
|
|
37
|
+
color: #faa857;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
.tab {
|
|
41
|
+
position: relative;
|
|
42
|
+
$activeColor: #337ab7;
|
|
43
|
+
$hoverColor: color.adjust($activeColor, $lightness: 30%);
|
|
44
|
+
|
|
45
|
+
.loader {
|
|
46
|
+
display: none;
|
|
47
|
+
background-color: color.adjust(#555, $lightness: 50%);
|
|
48
|
+
height: 2px;
|
|
49
|
+
position: absolute;
|
|
50
|
+
bottom: 0;
|
|
51
|
+
left: 0;
|
|
52
|
+
right: 100%;
|
|
53
|
+
animation-name: slide;
|
|
54
|
+
animation-duration: 2s;
|
|
55
|
+
animation-timing-function: ease;
|
|
56
|
+
animation-iteration-count: infinite;
|
|
57
|
+
}
|
|
58
|
+
@keyframes slide {
|
|
59
|
+
0% {
|
|
60
|
+
left: 0;
|
|
61
|
+
right: 100%;
|
|
62
|
+
}
|
|
63
|
+
70% {
|
|
64
|
+
left: 0;
|
|
65
|
+
right: 0;
|
|
66
|
+
}
|
|
67
|
+
100% {
|
|
68
|
+
left: 100%;
|
|
69
|
+
right: 0;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
&.active .loader {
|
|
73
|
+
background-color: $hoverColor;
|
|
74
|
+
}
|
|
75
|
+
&:hover .loader {
|
|
76
|
+
background-color: $activeColor;
|
|
77
|
+
}
|
|
78
|
+
&.querying .loader {
|
|
79
|
+
display: block;
|
|
80
|
+
}
|
|
81
|
+
&.active a {
|
|
82
|
+
border-bottom-color: $activeColor;
|
|
83
|
+
color: #555;
|
|
84
|
+
}
|
|
85
|
+
input {
|
|
86
|
+
display: none;
|
|
87
|
+
outline: none;
|
|
88
|
+
border: none;
|
|
89
|
+
}
|
|
90
|
+
&.renaming {
|
|
91
|
+
span {
|
|
92
|
+
display: none;
|
|
93
|
+
}
|
|
94
|
+
.closeTab {
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
input {
|
|
98
|
+
display: block;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
a {
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
color: color.adjust(#555, $lightness: 20%);
|
|
104
|
+
font-size: 15px;
|
|
105
|
+
line-height: 1.5rem;
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
min-width: 120px;
|
|
108
|
+
padding: 0px 24px 0px 30px;
|
|
109
|
+
white-space: nowrap;
|
|
110
|
+
overflow: hidden;
|
|
111
|
+
&:hover {
|
|
112
|
+
border-bottom-color: $hoverColor;
|
|
113
|
+
color: #555;
|
|
114
|
+
}
|
|
115
|
+
&:focus {
|
|
116
|
+
border-bottom-color: #faa857;
|
|
117
|
+
color: #555;
|
|
118
|
+
}
|
|
119
|
+
.closeTab {
|
|
120
|
+
color: #000;
|
|
121
|
+
margin-left: 7px;
|
|
122
|
+
font-size: 15px;
|
|
123
|
+
text-shadow: 0 1px 0 #fff;
|
|
124
|
+
opacity: 0.2;
|
|
125
|
+
font-weight: 700;
|
|
126
|
+
padding: 2px;
|
|
127
|
+
&:hover {
|
|
128
|
+
opacity: 0.5;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import Yasgui from "./";
|
|
2
|
+
import TabContextMenu from "./TabContextMenu";
|
|
3
|
+
import { hasClass, addClass, removeClass } from "@matdata/yasgui-utils";
|
|
4
|
+
const sortablejs = require("sortablejs");
|
|
5
|
+
require("./TabElements.scss");
|
|
6
|
+
export interface TabList {}
|
|
7
|
+
export class TabListEl {
|
|
8
|
+
private tabList: TabList;
|
|
9
|
+
private tabId: string;
|
|
10
|
+
private yasgui: Yasgui;
|
|
11
|
+
private renameEl?: HTMLInputElement;
|
|
12
|
+
private nameEl?: HTMLSpanElement;
|
|
13
|
+
public tabEl?: HTMLDivElement;
|
|
14
|
+
constructor(yasgui: Yasgui, tabList: TabList, tabId: string) {
|
|
15
|
+
this.tabList = tabList;
|
|
16
|
+
this.yasgui = yasgui;
|
|
17
|
+
this.tabId = tabId;
|
|
18
|
+
}
|
|
19
|
+
public delete() {
|
|
20
|
+
if (this.tabEl) {
|
|
21
|
+
this.tabList._tabsListEl?.removeChild(this.tabEl);
|
|
22
|
+
delete this.tabList._tabs[this.tabId];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
public startRename() {
|
|
26
|
+
if (this.renameEl) {
|
|
27
|
+
const tab = this.yasgui.getTab(this.tabId);
|
|
28
|
+
if (tab) {
|
|
29
|
+
this.renameEl.value = tab.name();
|
|
30
|
+
addClass(this.tabEl, "renaming");
|
|
31
|
+
this.renameEl.focus();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
public active(active: boolean) {
|
|
36
|
+
if (!this.tabEl) return;
|
|
37
|
+
if (active) {
|
|
38
|
+
addClass(this.tabEl, "active");
|
|
39
|
+
// add aria-properties
|
|
40
|
+
this.tabEl.children[0].setAttribute("aria-selected", "true");
|
|
41
|
+
this.tabEl.children[0].setAttribute("tabindex", "0");
|
|
42
|
+
} else {
|
|
43
|
+
removeClass(this.tabEl, "active");
|
|
44
|
+
// remove aria-properties
|
|
45
|
+
this.tabEl.children[0].setAttribute("aria-selected", "false");
|
|
46
|
+
this.tabEl.children[0].setAttribute("tabindex", "-1");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
public rename(name: string) {
|
|
50
|
+
if (this.nameEl) {
|
|
51
|
+
this.nameEl.textContent = name;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
public setAsQuerying(querying: boolean) {
|
|
55
|
+
if (querying) {
|
|
56
|
+
addClass(this.tabEl, "querying");
|
|
57
|
+
} else {
|
|
58
|
+
removeClass(this.tabEl, "querying");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
public draw(name: string) {
|
|
62
|
+
this.tabEl = document.createElement("div");
|
|
63
|
+
this.tabEl.setAttribute("role", "presentation");
|
|
64
|
+
this.tabEl.ondblclick = () => {
|
|
65
|
+
this.startRename();
|
|
66
|
+
};
|
|
67
|
+
addClass(this.tabEl, "tab");
|
|
68
|
+
this.tabEl.addEventListener("keydown", (e: KeyboardEvent) => {
|
|
69
|
+
if (e.code === "Delete") handleDeleteTab();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const handleDeleteTab = (e?: MouseEvent) => {
|
|
73
|
+
e?.preventDefault();
|
|
74
|
+
this.yasgui.getTab(this.tabId)?.close();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const tabLinkEl = document.createElement("a");
|
|
78
|
+
tabLinkEl.setAttribute("role", "tab");
|
|
79
|
+
tabLinkEl.href = "#" + this.tabId;
|
|
80
|
+
tabLinkEl.id = "tab-" + this.tabId; // use the id for the tabpanel which is tabId to set the actual tab id
|
|
81
|
+
tabLinkEl.setAttribute("aria-controls", this.tabId); // respective tabPanel id
|
|
82
|
+
tabLinkEl.addEventListener("blur", () => {
|
|
83
|
+
if (!this.tabEl) return;
|
|
84
|
+
if (this.tabEl.classList.contains("active")) {
|
|
85
|
+
tabLinkEl.setAttribute("tabindex", "0");
|
|
86
|
+
} else {
|
|
87
|
+
tabLinkEl.setAttribute("tabindex", "-1");
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
tabLinkEl.addEventListener("focus", () => {
|
|
91
|
+
if (!this.tabEl) return;
|
|
92
|
+
if (this.tabEl.classList.contains("active")) {
|
|
93
|
+
const allTabs = Object.keys(this.tabList._tabs);
|
|
94
|
+
const currentTabIndex = allTabs.indexOf(this.tabId);
|
|
95
|
+
this.tabList.tabEntryIndex = currentTabIndex;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// if (this.yasgui.persistentConfig.tabIsActive(this.tabId)) {
|
|
99
|
+
// this.yasgui.store.dispatch(selectTab(this.tabId))
|
|
100
|
+
// }
|
|
101
|
+
tabLinkEl.addEventListener("click", (e) => {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
this.yasgui.selectTabId(this.tabId);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
//tab name
|
|
107
|
+
this.nameEl = document.createElement("span");
|
|
108
|
+
this.nameEl.textContent = name;
|
|
109
|
+
tabLinkEl.appendChild(this.nameEl);
|
|
110
|
+
|
|
111
|
+
//tab close btn
|
|
112
|
+
const closeBtn = document.createElement("div");
|
|
113
|
+
closeBtn.innerHTML = "✖";
|
|
114
|
+
closeBtn.title = "Close tab";
|
|
115
|
+
closeBtn.setAttribute("tabindex", "-1");
|
|
116
|
+
closeBtn.setAttribute("aria-hidden", "true");
|
|
117
|
+
addClass(closeBtn, "closeTab");
|
|
118
|
+
closeBtn.addEventListener("click", handleDeleteTab);
|
|
119
|
+
tabLinkEl.appendChild(closeBtn);
|
|
120
|
+
|
|
121
|
+
const renameEl = (this.renameEl = document.createElement("input"));
|
|
122
|
+
renameEl.type = "text";
|
|
123
|
+
renameEl.value = name;
|
|
124
|
+
renameEl.onkeyup = (event) => {
|
|
125
|
+
if (event.key === "Enter") {
|
|
126
|
+
this.yasgui.getTab(this.tabId)?.setName(renameEl.value);
|
|
127
|
+
removeClass(this.tabEl, "renaming");
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
renameEl.onblur = () => {
|
|
131
|
+
this.yasgui.getTab(this.tabId)?.setName(renameEl.value);
|
|
132
|
+
removeClass(this.tabEl, "renaming");
|
|
133
|
+
};
|
|
134
|
+
tabLinkEl.appendChild(this.renameEl);
|
|
135
|
+
tabLinkEl.oncontextmenu = (ev: MouseEvent) => {
|
|
136
|
+
// Close possible old
|
|
137
|
+
this.tabList.tabContextMenu?.closeConfigMenu();
|
|
138
|
+
this.openTabConfigMenu(ev);
|
|
139
|
+
ev.preventDefault();
|
|
140
|
+
ev.stopPropagation();
|
|
141
|
+
};
|
|
142
|
+
this.tabEl.appendChild(tabLinkEl);
|
|
143
|
+
|
|
144
|
+
//draw loading animation overlay
|
|
145
|
+
const loaderEl = document.createElement("div");
|
|
146
|
+
addClass(loaderEl, "loader");
|
|
147
|
+
this.tabEl.appendChild(loaderEl);
|
|
148
|
+
|
|
149
|
+
return this.tabEl;
|
|
150
|
+
}
|
|
151
|
+
private openTabConfigMenu(event: MouseEvent) {
|
|
152
|
+
this.tabList.tabContextMenu?.openConfigMenu(this.tabId, this, event);
|
|
153
|
+
}
|
|
154
|
+
redrawContextMenu() {
|
|
155
|
+
this.tabList.tabContextMenu?.redraw();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export class TabList {
|
|
160
|
+
yasgui: Yasgui;
|
|
161
|
+
|
|
162
|
+
private _selectedTab?: string;
|
|
163
|
+
private addTabEl?: HTMLDivElement;
|
|
164
|
+
public _tabs: { [tabId: string]: TabListEl } = {};
|
|
165
|
+
public _tabsListEl?: HTMLDivElement;
|
|
166
|
+
public tabContextMenu?: TabContextMenu;
|
|
167
|
+
public tabEntryIndex: number | undefined;
|
|
168
|
+
|
|
169
|
+
constructor(yasgui: Yasgui) {
|
|
170
|
+
this.yasgui = yasgui;
|
|
171
|
+
this.registerListeners();
|
|
172
|
+
this.tabEntryIndex = this.getActiveIndex();
|
|
173
|
+
}
|
|
174
|
+
get(tabId: string) {
|
|
175
|
+
return this._tabs[tabId];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private registerListeners() {
|
|
179
|
+
this.yasgui.on("query", (_yasgui, tab) => {
|
|
180
|
+
const id = tab.getId();
|
|
181
|
+
if (this._tabs[id]) {
|
|
182
|
+
this._tabs[id].setAsQuerying(true);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
this.yasgui.on("queryResponse", (_yasgui, tab) => {
|
|
186
|
+
const id = tab.getId();
|
|
187
|
+
if (this._tabs[id]) {
|
|
188
|
+
this._tabs[id].setAsQuerying(false);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
this.yasgui.on("queryAbort", (_yasgui, tab) => {
|
|
192
|
+
const id = tab.getId();
|
|
193
|
+
if (this._tabs[id]) {
|
|
194
|
+
this._tabs[id].setAsQuerying(false);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
private getActiveIndex() {
|
|
199
|
+
if (!this._selectedTab) return;
|
|
200
|
+
const allTabs = Object.keys(this._tabs);
|
|
201
|
+
const currentTabIndex = allTabs.indexOf(this._selectedTab);
|
|
202
|
+
return currentTabIndex;
|
|
203
|
+
}
|
|
204
|
+
private handleKeydownArrowKeys = (e: KeyboardEvent) => {
|
|
205
|
+
if (e.code === "ArrowLeft" || e.code === "ArrowRight") {
|
|
206
|
+
if (!this._tabsListEl) return;
|
|
207
|
+
const numOfChildren = this._tabsListEl.childElementCount;
|
|
208
|
+
if (typeof this.tabEntryIndex !== "number") return;
|
|
209
|
+
const tabEntryDiv = this._tabsListEl.children[this.tabEntryIndex];
|
|
210
|
+
// If the current tab does not have active set its tabindex to -1
|
|
211
|
+
if (!tabEntryDiv.classList.contains("active")) {
|
|
212
|
+
tabEntryDiv.children[0].setAttribute("tabindex", "-1"); // cur tab removed from tab index
|
|
213
|
+
}
|
|
214
|
+
if (e.code === "ArrowLeft") {
|
|
215
|
+
this.tabEntryIndex--;
|
|
216
|
+
if (this.tabEntryIndex < 0) {
|
|
217
|
+
this.tabEntryIndex = numOfChildren - 1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (e.code === "ArrowRight") {
|
|
221
|
+
this.tabEntryIndex++;
|
|
222
|
+
if (this.tabEntryIndex >= numOfChildren) {
|
|
223
|
+
this.tabEntryIndex = 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const newTabEntryDiv = this._tabsListEl.children[this.tabEntryIndex];
|
|
227
|
+
newTabEntryDiv.children[0].setAttribute("tabindex", "0");
|
|
228
|
+
(newTabEntryDiv.children[0] as HTMLElement).focus(); // focus on the a tag inside the div for click event
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
drawTabsList() {
|
|
232
|
+
this._tabsListEl = document.createElement("div");
|
|
233
|
+
addClass(this._tabsListEl, "tabsList");
|
|
234
|
+
this._tabsListEl.setAttribute("role", "tablist");
|
|
235
|
+
this._tabsListEl.addEventListener("keydown", this.handleKeydownArrowKeys);
|
|
236
|
+
|
|
237
|
+
sortablejs.default.create(this._tabsListEl, {
|
|
238
|
+
group: "tabList",
|
|
239
|
+
animation: 100,
|
|
240
|
+
onUpdate: (_ev: any) => {
|
|
241
|
+
const tabs = this.deriveTabOrderFromEls();
|
|
242
|
+
this.yasgui.emit("tabOrderChanged", this.yasgui, tabs);
|
|
243
|
+
this.yasgui.persistentConfig.setTabOrder(tabs);
|
|
244
|
+
},
|
|
245
|
+
filter: ".addTab",
|
|
246
|
+
onMove: (ev: any, _origEv: any) => {
|
|
247
|
+
return hasClass(ev.related, "tab");
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
this.addTabEl = document.createElement("div");
|
|
252
|
+
this.addTabEl.setAttribute("role", "presentation");
|
|
253
|
+
|
|
254
|
+
const addTabLink = document.createElement("button");
|
|
255
|
+
addTabLink.className = "addTab";
|
|
256
|
+
addTabLink.textContent = "+";
|
|
257
|
+
addTabLink.title = "Add tab";
|
|
258
|
+
addTabLink.setAttribute("aria-label", "Add a new tab");
|
|
259
|
+
addTabLink.addEventListener("click", this.handleAddNewTab);
|
|
260
|
+
addTabLink.addEventListener("focus", () => {
|
|
261
|
+
// sets aria tabEntryIndex to active tab
|
|
262
|
+
// this.tabEntryIndex = this.getActiveIndex();
|
|
263
|
+
if (!this._tabsListEl) return;
|
|
264
|
+
this.tabEntryIndex = this._tabsListEl.childElementCount - 1; // sets tabEntry to add tab, visually makes sense, not sure about accessibility-wise
|
|
265
|
+
});
|
|
266
|
+
addTabLink.addEventListener("blur", () => {
|
|
267
|
+
addTabLink.setAttribute("tabindex", "0"); // maintains tabability
|
|
268
|
+
});
|
|
269
|
+
this.addTabEl.appendChild(addTabLink);
|
|
270
|
+
this._tabsListEl.appendChild(this.addTabEl);
|
|
271
|
+
this.tabContextMenu = TabContextMenu.get(
|
|
272
|
+
this.yasgui,
|
|
273
|
+
this.yasgui.config.contextMenuContainer ? this.yasgui.config.contextMenuContainer : this._tabsListEl,
|
|
274
|
+
);
|
|
275
|
+
return this._tabsListEl;
|
|
276
|
+
}
|
|
277
|
+
handleAddNewTab = (event: Event) => {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
this.yasgui.addTab(true);
|
|
280
|
+
};
|
|
281
|
+
// drawPanels() {
|
|
282
|
+
// this.tabPanelsEl = document.createElement("div");
|
|
283
|
+
// return this.tabsListEl;
|
|
284
|
+
// }
|
|
285
|
+
public addTab(tabId: string, index?: number) {
|
|
286
|
+
return this.drawTab(tabId, index);
|
|
287
|
+
}
|
|
288
|
+
public deriveTabOrderFromEls() {
|
|
289
|
+
const tabs: string[] = [];
|
|
290
|
+
if (this._tabsListEl) {
|
|
291
|
+
for (let i = 0; i < this._tabsListEl.children.length; i++) {
|
|
292
|
+
const child = this._tabsListEl.children[i]; //this is the tab div
|
|
293
|
+
const anchorTag = child.children[0]; //this one has an href
|
|
294
|
+
if (anchorTag) {
|
|
295
|
+
const href = (<HTMLAnchorElement>anchorTag).href;
|
|
296
|
+
if (href && href.indexOf("#") >= 0) {
|
|
297
|
+
tabs.push(href.substr(href.indexOf("#") + 1));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return tabs;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
public selectTab(tabId: string) {
|
|
306
|
+
this._selectedTab = tabId;
|
|
307
|
+
for (const id in this._tabs) {
|
|
308
|
+
this._tabs[id].active(this._selectedTab === id);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public drawTab(tabId: string, index?: number) {
|
|
313
|
+
this._tabs[tabId] = new TabListEl(this.yasgui, this, tabId);
|
|
314
|
+
const tabConf = this.yasgui.persistentConfig.getTab(tabId);
|
|
315
|
+
if (index !== undefined && index < this.yasgui.persistentConfig.getTabs().length - 1) {
|
|
316
|
+
this._tabsListEl?.insertBefore(
|
|
317
|
+
this._tabs[tabId].draw(tabConf.name),
|
|
318
|
+
this._tabs[this.yasgui.persistentConfig.getTabs()[index + 1]].tabEl || null,
|
|
319
|
+
);
|
|
320
|
+
} else {
|
|
321
|
+
this._tabsListEl?.insertBefore(this._tabs[tabId].draw(tabConf.name), this.addTabEl || null);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
public destroy() {
|
|
325
|
+
for (const tabId in this._tabs) {
|
|
326
|
+
const tab = this._tabs[tabId];
|
|
327
|
+
tab.delete();
|
|
328
|
+
}
|
|
329
|
+
this._tabs = {};
|
|
330
|
+
this.tabContextMenu?.destroy();
|
|
331
|
+
this._tabsListEl?.remove();
|
|
332
|
+
this._tabsListEl = undefined;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export default TabList;
|