diff --git a/src/myfasthtml/assets/myfasthtml.js b/src/myfasthtml/assets/myfasthtml.js
index 0f81e4e..7b09487 100644
--- a/src/myfasthtml/assets/myfasthtml.js
+++ b/src/myfasthtml/assets/myfasthtml.js
@@ -152,7 +152,6 @@ function initLayoutResizer(layoutId) {
// Re-initialize after HTMX swaps within this layout
layoutElement.addEventListener('htmx:afterSwap', function (event) {
- console.log('Layout swapped:', event.detail.target);
initResizers();
});
}
@@ -213,7 +212,7 @@ function initBoundaries(elementId, updateUrl) {
* This function is called when switching between tabs to update both the content visibility
* and the tab button states.
*
- * @param {string} controllerId - The ID of the tabs controller element (format: "{managerId}-content-controller")
+ * @param {string} controllerId - The ID of the tabs controller element (format: "{managerId}-controller")
*/
function updateTabs(controllerId) {
const controller = document.getElementById(controllerId);
@@ -228,8 +227,8 @@ function updateTabs(controllerId) {
return;
}
- // Extract manager ID from controller ID (remove '-content-controller' suffix)
- const managerId = controllerId.replace('-content-controller', '');
+ // Extract manager ID from controller ID (remove '-controller' suffix)
+ const managerId = controllerId.replace('-controller', '');
// Hide all tab contents for this manager
const contentWrapper = document.getElementById(`${managerId}-content-wrapper`);
diff --git a/src/myfasthtml/controls/TabsManager.py b/src/myfasthtml/controls/TabsManager.py
index 6b5ec55..eafe18f 100644
--- a/src/myfasthtml/controls/TabsManager.py
+++ b/src/myfasthtml/controls/TabsManager.py
@@ -60,7 +60,7 @@ class Commands(BaseCommands):
def show_tab(self, tab_id):
return Command(f"{self._prefix}ShowTab",
"Activate or show a specific tab",
- self._owner.show_tab, tab_id).htmx(target=f"#{self._id}-content-controller", swap="outerHTML")
+ self._owner.show_tab, tab_id).htmx(target=f"#{self._id}-controller", swap="outerHTML")
def close_tab(self, tab_id):
return Command(f"{self._prefix}CloseTab",
@@ -71,20 +71,10 @@ class Commands(BaseCommands):
return (Command(f"{self._prefix}AddTab",
"Add a new tab",
self._owner.on_new_tab, label, component, auto_increment).
- htmx(target=f"#{self._id}-content-controller"))
-
- def update_boundaries(self):
- return Command(f"{self._prefix}UpdateBoundaries",
- "Update component boundaries",
- self._owner.update_boundaries).htmx(target=None)
+ htmx(target=f"#{self._id}-controller"))
class TabsManager(MultipleInstance):
- # Constants for width calculation
- TAB_CHAR_WIDTH = 8 # pixels per character
- TAB_PADDING = 40 # padding + close button space
- TAB_MIN_WIDTH = 80 # minimum tab width
- DROPDOWN_BTN_WIDTH = 40 # width of the dropdown button
_tab_count = 0
def __init__(self, session, _id=None):
@@ -264,8 +254,8 @@ class TabsManager(MultipleInstance):
def _mk_tabs_controller(self):
return Div(
- Div(id=f"{self._id}-content-controller", data_active_tab=f"{self._state.active_tab}"),
- Script(f'updateTabs("{self._id}-content-controller");'),
+ Div(id=f"{self._id}-controller", data_active_tab=f"{self._state.active_tab}"),
+ Script(f'updateTabs("{self._id}-controller");'),
)
def _mk_tabs_header(self, oob=False):
diff --git a/tests/controls/test_tabsmanager.py b/tests/controls/test_tabsmanager.py
index 41d1326..aae41ed 100644
--- a/tests/controls/test_tabsmanager.py
+++ b/tests/controls/test_tabsmanager.py
@@ -1,9 +1,10 @@
import pytest
from fasthtml.components import *
+from fasthtml.xtend import Script
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.core.instances import InstancesManager
-from myfasthtml.test.matcher import matches, NoChildren
+from myfasthtml.test.matcher import matches, NoChildren, StartsWith
from .conftest import session
@@ -20,11 +21,12 @@ class TestTabsManagerBehaviour:
assert from_instance_manager == tabs_manager
def test_i_can_add_tab(self, tabs_manager):
- tab_id = tabs_manager.add_tab("Users", Div("Content 1"))
+ tab_id = tabs_manager.add_tab("Tab1", Div("Content 1"))
assert tab_id is not None
assert tab_id in tabs_manager.get_state().tabs
- assert tabs_manager.get_state().tabs[tab_id]["label"] == "Users"
+ assert tabs_manager.get_state().tabs[tab_id]["label"] == "Tab1"
+ assert tabs_manager.get_state().tabs[tab_id]["id"] == tab_id
assert tabs_manager.get_state().tabs[tab_id]["component_type"] is None # Div is not BaseInstance
assert tabs_manager.get_state().tabs[tab_id]["component_id"] is None # Div is not BaseInstance
assert tabs_manager.get_state().tabs_order == [tab_id]
@@ -37,34 +39,57 @@ class TestTabsManagerBehaviour:
assert len(tabs_manager.get_state().tabs) == 2
assert tabs_manager.get_state().tabs_order == [tab_id1, tab_id2]
assert tabs_manager.get_state().active_tab == tab_id2
+
+ def test_i_can_show_tab(self, tabs_manager):
+ tab_id1 = tabs_manager.add_tab("Tab1", Div("Content 1"))
+ tab_id2 = tabs_manager.add_tab("Tab2", Div("Content 2"))
+
+ assert tabs_manager.get_state().active_tab == tab_id2 # last crated tab is active
+
+ tabs_manager.show_tab(tab_id1)
+ assert tabs_manager.get_state().active_tab == tab_id1
+
+ def test_i_can_close_tab(self, tabs_manager):
+ tab_id1 = tabs_manager.add_tab("Tab1", Div("Content 1"))
+ tab_id2 = tabs_manager.add_tab("Tab2", Div("Content 2"))
+ tab_id3 = tabs_manager.add_tab("Tab3", Div("Content 3"))
+
+ tabs_manager.close_tab(tab_id2)
+
+ assert len(tabs_manager.get_state().tabs) == 2
+ assert [tab_id for tab_id in tabs_manager.get_state().tabs] == [tab_id1, tab_id3]
+ assert tabs_manager.get_state().tabs_order == [tab_id1, tab_id3]
+ assert tabs_manager.get_state().active_tab == tab_id3 # last tab stays active
+
+ def test_i_still_have_an_active_tab_after_close(self, tabs_manager):
+ tab_id1 = tabs_manager.add_tab("Tab1", Div("Content 1"))
+ tab_id2 = tabs_manager.add_tab("Tab2", Div("Content 2"))
+ tab_id3 = tabs_manager.add_tab("Tab3", Div("Content 3"))
+
+ tabs_manager.close_tab(tab_id3) # close the currently active tab
+ assert tabs_manager.get_state().active_tab == tab_id1 # default to the first tab
class TestTabsManagerRender:
def test_i_can_render_when_no_tabs(self, tabs_manager):
res = tabs_manager.render()
- expected = Div(
- Div(NoChildren(), id=f"{tabs_manager.get_id()}-header"),
- Div(id=f"{tabs_manager.get_id()}-content"),
- id=tabs_manager.get_id(),
- )
-
- assert matches(res, expected)
-
- def test_i_can_render_when_one_tab(self, tabs_manager):
- tabs_manager.add_tab("Users", Div("Content 1"))
- res = tabs_manager.render()
-
expected = Div(
Div(
- Button(),
- id=f"{tabs_manager.get_id()}-header"
+ Div(id=f"{tabs_manager.get_id()}-controller"),
+ Script(f'updateTabs("{tabs_manager.get_id()}-controller");'),
),
Div(
- Div("Content 1")
+ Div(NoChildren(), id=f"{tabs_manager.get_id()}-header"),
+ id=f"{tabs_manager.get_id()}-header-wrapper"
+ ),
+ Div(
+ Div(id=f"{tabs_manager.get_id()}-None-content"),
+ id=f"{tabs_manager.get_id()}-content-wrapper"
),
id=tabs_manager.get_id(),
)
+
assert matches(res, expected)
def test_i_can_render_when_multiple_tabs(self, tabs_manager):
@@ -75,13 +100,22 @@ class TestTabsManagerRender:
expected = Div(
Div(
- Button(),
- Button(),
- Button(),
- id=f"{tabs_manager.get_id()}-header"
+ Div(id=f"{tabs_manager.get_id()}-controller"),
+ Script(f'updateTabs("{tabs_manager.get_id()}-controller");'),
),
Div(
- Div("Content 3")
+ Div(
+ Div(), # tab_button
+ Div(), # tab_button
+ Div(), # tab_button
+ id=f"{tabs_manager.get_id()}-header"
+ ),
+ id=f"{tabs_manager.get_id()}-header-wrapper"
+ ),
+ Div(
+ Div(id=StartsWith(tabs_manager.get_id())),
+ # Lasy loading for the other contents
+ id=f"{tabs_manager.get_id()}-content-wrapper"
),
id=tabs_manager.get_id(),
)