Skip to content

Commit 2e70e86

Browse files
feat: Add tab change event to tab container + binding for active tab (#1149)
* feat: Add wf-tab-change event to tab container * Update src/ui/src/components/core/layout/CoreTabs.vue Co-authored-by: Alexandre Rousseau <alexandre@rsseau.fr> * add: Make active tab bindable in CoreTabs * fix: infinite loops with two-way tab binding * fix: tab activation logic in CoreTabs component * fix: improve tab activation fallback logic in CoreTabs component --------- Co-authored-by: Alexandre Rousseau <alexandre@rsseau.fr>
1 parent d75f885 commit 2e70e86

File tree

2 files changed

+94
-13
lines changed

2 files changed

+94
-13
lines changed

src/ui/src/components/core/layout/CoreTab.vue

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div v-show="isVisible" class="CoreTab">
2+
<div v-show="isVisible" ref="rootEl" class="CoreTab">
33
<button
44
v-if="isTabBit"
55
class="bit"
@@ -146,9 +146,8 @@ const getMatchingTabInstancePath = () => {
146146
147147
const activateTab = () => {
148148
const tabContainerData = getTabContainerData();
149-
tabContainerData.value = {
150-
activeTab: getMatchingTabInstancePath(),
151-
};
149+
tabContainerData.value.activeTab = getMatchingTabInstancePath();
150+
tabContainerData.value.activeTabName = fields.name.value;
152151
};
153152
154153
const checkIfTabIsParent = (childId: Component["id"]): boolean => {
@@ -180,12 +179,13 @@ const isTabActive = computed(() => {
180179
});
181180
182181
onBeforeMount(() => {
183-
if (isTabBit.value) return;
182+
if (!isTabBit.value) return;
184183
const tabContainerData = getTabContainerData();
185-
const activeTab = tabContainerData.value?.activeTab;
186-
if (activeTab) return;
187-
if (!isComponentVisible(componentId, instancePath)) return;
188-
tabContainerData.value = { activeTab: instancePath };
184+
tabContainerData.value.tabs.push({
185+
name: fields.name.value,
186+
instancePath: getMatchingTabInstancePath(),
187+
componentId: componentId,
188+
});
189189
});
190190
</script>
191191

src/ui/src/components/core/layout/CoreTabs.vue

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div class="CoreTabs">
2+
<div ref="rootInstance" class="CoreTabs">
33
<nav
44
class="tabSelector horizontal"
55
data-writer-cage
@@ -26,6 +26,14 @@ import {
2626
buttonShadow,
2727
cssClasses,
2828
} from "@/renderer/sharedStyleFields";
29+
import { useFormValueBroker } from "@/renderer/useFormValueBroker";
30+
31+
const tabChangeHandlerStub = `
32+
def tab_change_handler(state, payload):
33+
34+
# The payload contains the name of the newly activated tab
35+
36+
state["active_tab"] = payload`;
2937
3038
const description =
3139
"A container component for organising and displaying Tab components in a tabbed interface.";
@@ -36,6 +44,14 @@ export default {
3644
description,
3745
category: "Layout",
3846
allowedChildrenTypes: ["tab", "repeater"],
47+
events: {
48+
"wf-tab-change": {
49+
desc: "Sent when the active tab changes.",
50+
stub: tabChangeHandlerStub.trim(),
51+
eventPayloadExample: "Tab Name",
52+
bindable: true,
53+
},
54+
},
3955
fields: {
4056
accentColor,
4157
primaryTextColor,
@@ -52,11 +68,76 @@ export default {
5268
};
5369
</script>
5470
<script setup lang="ts">
55-
import { inject } from "vue";
71+
import { ComponentPublicInstance, inject, onMounted, ref, watch } from "vue";
72+
import { useEvaluator } from "@/renderer/useEvaluator";
5673
import injectionKeys from "@/injectionKeys";
57-
74+
const rootInstance = ref<ComponentPublicInstance | null>(null);
75+
const wf = inject(injectionKeys.core);
76+
const instancePath = inject(injectionKeys.instancePath);
5877
const instanceData = inject(injectionKeys.instanceData);
59-
instanceData.at(-1).value = { activeTab: undefined };
78+
const containerState = instanceData.at(-1);
79+
const { isComponentVisible } = useEvaluator(wf);
80+
81+
const { formValue, handleInput } = useFormValueBroker(
82+
wf,
83+
instancePath,
84+
rootInstance,
85+
);
86+
87+
containerState.value = {
88+
activeTab: undefined,
89+
activeTabName: formValue.value,
90+
tabs: [],
91+
};
92+
93+
watch(
94+
() => containerState.value?.activeTabName,
95+
(activeTabName) => {
96+
if (!rootInstance.value) return;
97+
if (activeTabName === formValue.value) return;
98+
handleInput(activeTabName, "wf-tab-change");
99+
},
100+
);
101+
102+
watch(formValue, (newTabName) => {
103+
if (newTabName === containerState.value.activeTabName) return;
104+
if (!containerState.value.tabs?.length) return;
105+
106+
const visibleTabs = containerState.value.tabs.filter((t) =>
107+
isComponentVisible(t.componentId, t.instancePath),
108+
);
109+
if (!visibleTabs.length) return;
110+
111+
let tabToActivate = visibleTabs.find((t) => t.name === newTabName);
112+
113+
if (!tabToActivate) {
114+
const currentActiveInState = visibleTabs.find(
115+
(t) => t.name === containerState.value.activeTabName,
116+
);
117+
tabToActivate = currentActiveInState ?? visibleTabs[0];
118+
}
119+
120+
containerState.value.activeTab = tabToActivate.instancePath;
121+
containerState.value.activeTabName = tabToActivate.name;
122+
});
123+
124+
onMounted(() => {
125+
if (containerState.value.activeTab) return;
126+
if (!containerState.value.tabs) return;
127+
128+
const visibleTabs = containerState.value.tabs.filter((t) =>
129+
isComponentVisible(t.componentId, t.instancePath),
130+
);
131+
if (visibleTabs.length === 0) return;
132+
133+
const initialActiveTabName = containerState.value.activeTabName;
134+
const tabToActivate =
135+
visibleTabs.find((t) => t.name === initialActiveTabName) ??
136+
visibleTabs[0];
137+
138+
containerState.value.activeTab = tabToActivate.instancePath;
139+
containerState.value.activeTabName = tabToActivate.name;
140+
});
60141
</script>
61142

62143
<style scoped>

0 commit comments

Comments
 (0)