diff --git a/app/web/package.json b/app/web/package.json
index 2e32ca0302..649cc2730d 100644
--- a/app/web/package.json
+++ b/app/web/package.json
@@ -73,7 +73,8 @@
"vanilla-picker": "^2.12.1",
"vue": "^3.3.4",
"vue-konva": "^3.0.1",
- "vue-router": "^4.1.6"
+ "vue-router": "^4.1.6",
+ "vue-safe-teleport": "^0.1.2"
},
"devDependencies": {
"@iconify/json": "^2.1.142",
diff --git a/app/web/src/main.ts b/app/web/src/main.ts
index f460fb9378..1425f935d4 100644
--- a/app/web/src/main.ts
+++ b/app/web/src/main.ts
@@ -2,6 +2,7 @@ import { createApp } from "vue";
import FloatingVue from "floating-vue";
import VueKonva from "vue-konva";
import { createHead } from "@vueuse/head";
+import VueSafeTeleport from "vue-safe-teleport";
import "@si/vue-lib/tailwind/main.css";
import "@si/vue-lib/tailwind/tailwind.css";
@@ -24,4 +25,6 @@ app.use(FloatingVue, { container: "#app-layout" });
// TODO: fork the lib and set it up so we can import individual components
app.use(VueKonva);
+app.use(VueSafeTeleport);
+
app.mount("#app");
diff --git a/lib/vue-lib/src/design-system/tabs/TabGroup.vue b/lib/vue-lib/src/design-system/tabs/TabGroup.vue
index 4fbc4d046c..7719dd8191 100644
--- a/lib/vue-lib/src/design-system/tabs/TabGroup.vue
+++ b/lib/vue-lib/src/design-system/tabs/TabGroup.vue
@@ -58,7 +58,7 @@
{{ tab.props.label }}
+
@@ -119,6 +119,7 @@ type TabGroupContext = {
registerTab(id: string, component: TabGroupItemDefinition): void;
unregisterTab(id: string): void;
selectTab(id?: string): void;
+ tabExists(id?: string): boolean;
teleportId: string;
};
@@ -208,6 +209,10 @@ function registerTab(slug: string, component: TabGroupItemDefinition) {
autoSelectTab(true);
});
}
+
+ if (pendingTabSlug.value && pendingTabSlug.value === slug) {
+ selectTab(slug);
+ }
}
function unregisterTab(slug: string) {
if (unmounting.value) return;
@@ -229,6 +234,11 @@ function refreshSettingsFromTabs() {
// currently there are no settings here - any child settings to set on the parent would go here
}
+function tabExists(slug?: string) {
+ return !!(slug && tabs[slug]);
+}
+
+const pendingTabSlug = ref();
const lastSelectedTabIndex = ref(0);
function selectTab(slug?: string) {
if (unmounting.value) return;
@@ -241,8 +251,16 @@ function selectTab(slug?: string) {
}
// select the tab
- if (slug && tabs[slug]) selectedTabSlug.value = slug;
- else selectedTabSlug.value = undefined;
+ if (slug && tabs[slug]) {
+ selectedTabSlug.value = slug;
+ pendingTabSlug.value = undefined;
+ } else {
+ // If the tab is not yet present, we mark this as the pending tab slug. When
+ // registerTab is called with a matching slug, that tab will be selected.
+ // Any other tab selection clears the pending tab slug
+ selectedTabSlug.value = undefined;
+ pendingTabSlug.value = slug;
+ }
lastSelectedTabIndex.value = _.indexOf(
orderedTabSlugs.value,
@@ -279,7 +297,7 @@ function selectTab(slug?: string) {
);
}
}
- }
+ }
// emit new selected tab to parent in case it needs it, for example to sync the URL
emit("update:selectedTab", selectedTabSlug.value);
@@ -294,6 +312,7 @@ const rememberLastTabStorageKey = computed(() => {
});
function autoSelectTab(isInitialSelection = false) {
+ pendingTabSlug.value = undefined;
if (isNoTabs.value) {
// can't select anything if there are no tabs
// selectTab();
@@ -379,9 +398,10 @@ const context = {
registerTab,
unregisterTab,
selectTab,
+ tabExists,
teleportId,
};
provide(TabGroupContextInjectionKey, context);
-defineExpose({ selectTab });
+defineExpose({ selectTab, tabExists });
diff --git a/lib/vue-lib/src/design-system/tabs/TabGroupItem.vue b/lib/vue-lib/src/design-system/tabs/TabGroupItem.vue
index e85bda69de..cb4f52c7c9 100644
--- a/lib/vue-lib/src/design-system/tabs/TabGroupItem.vue
+++ b/lib/vue-lib/src/design-system/tabs/TabGroupItem.vue
@@ -6,9 +6,9 @@
-
+
-
+
@@ -18,7 +18,7 @@ import { useTabGroupContext } from "./TabGroup.vue";
import type { Slot } from "vue";
export type TabGroupItemDefinition = {
- props: { slug: string; label: string };
+ props: { slug: string; label: string; uncloseable: boolean };
slots: {
default?: Slot;
label?: Slot;
@@ -29,6 +29,7 @@ export type TabGroupItemDefinition = {
const props = defineProps({
label: { type: String },
+ uncloseable: { type: Boolean, default: false },
slug: { type: String, default: () => `tab-group-${idCounter++}` },
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c0b38f6baa..5584691637 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -160,7 +160,7 @@ importers:
version: 3.2.4
axios:
specifier: ^0.27.2
- version: 0.27.2(debug@4.3.4)
+ version: 0.27.2
clsx:
specifier: ^1.2.1
version: 1.2.1
@@ -239,6 +239,9 @@ importers:
vue-router:
specifier: ^4.1.6
version: 4.1.6(vue@3.3.4)
+ vue-safe-teleport:
+ specifier: ^0.1.2
+ version: 0.1.2(vue@3.3.4)
devDependencies:
'@iconify/json':
specifier: ^2.1.142
@@ -1640,7 +1643,7 @@ packages:
'@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.5.2
'@iconify/types': 2.0.0
- debug: 4.3.4(supports-color@8.1.1)
+ debug: 4.3.4
kolorist: 1.6.0
local-pkg: 0.4.2
transitivePeerDependencies:
@@ -3730,7 +3733,7 @@ packages:
/@typescript/vfs@1.4.0:
resolution: {integrity: sha512-Pood7yv5YWMIX+yCHo176OnF8WUlKGImFG7XlsuH14Zb1YN5+dYD3uUtS7lqZtsH7tAveNUi2NzdpQCN0yRbaw==}
dependencies:
- debug: 4.3.4(supports-color@8.1.1)
+ debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: false
@@ -4766,6 +4769,15 @@ packages:
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
dev: true
+ /axios@0.27.2:
+ resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
+ dependencies:
+ follow-redirects: 1.15.2
+ form-data: 4.0.0
+ transitivePeerDependencies:
+ - debug
+ dev: false
+
/axios@0.27.2(debug@4.3.4):
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
dependencies:
@@ -6160,6 +6172,17 @@ packages:
supports-color: 9.3.1
dev: true
+ /debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.3
+ optional: true
+
/debug@3.2.7(supports-color@8.1.1):
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -6170,6 +6193,18 @@ packages:
dependencies:
ms: 2.1.3
supports-color: 8.1.1
+ dev: true
+
+ /debug@4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
/debug@4.3.4(supports-color@8.1.1):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
@@ -7969,6 +8004,16 @@ packages:
from2: 2.3.0
dev: true
+ /follow-redirects@1.15.2:
+ resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+ dev: false
+
/follow-redirects@1.15.2(debug@4.3.4):
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
@@ -11339,7 +11384,7 @@ packages:
hasBin: true
requiresBuild: true
dependencies:
- debug: 3.2.7(supports-color@8.1.1)
+ debug: 3.2.7
iconv-lite: 0.6.3
sax: 1.2.4
transitivePeerDependencies:
@@ -15022,7 +15067,7 @@ packages:
'@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.6.3
'@iconify/utils': 2.0.2
- debug: 4.3.4(supports-color@8.1.1)
+ debug: 4.3.4
kolorist: 1.6.0
local-pkg: 0.4.2
unplugin: 1.0.0
@@ -15483,6 +15528,14 @@ packages:
vue: 3.3.4
dev: false
+ /vue-safe-teleport@0.1.2(vue@3.3.4):
+ resolution: {integrity: sha512-L6S/ALd5I7hXWi2T5HETHKCW9bku0hNx4ocIWRsk46h1IfNvjXxtLE9ECV4SDJFkcTAn3pMf9yxftBYLoZ3USQ==}
+ peerDependencies:
+ vue: ^3.2.0
+ dependencies:
+ vue: 3.3.4
+ dev: false
+
/vue-template-compiler@2.7.14:
resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
dependencies: