Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/panel_splitjs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def _validate(self, val):
return
if self.length is not None and isinstance(val, tuple) and len(val) != self.length:
raise ValueError(f"Size parameter {self.name!r} must have length {self.length}")
if not (isinstance(val, (int, float)) or (isinstance(val, tuple) and all(isinstance(v, (int, float)) for v in val))):
if not (isinstance(val, (int, float)) or (isinstance(val, tuple) and all(isinstance(v, (int, float)) or v is None for v in val))):
raise ValueError(f"Size parameter {self.name!r} only takes int or float values")


Expand Down
4 changes: 0 additions & 4 deletions src/panel_splitjs/dist/css/splitjs.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@
width: 100%;
}

/* Ensure buttons stay visible even when panels are collapsed */
.split.single-split > div:first-child {
min-width: 0 !important; /* Override any minimum width */
}

.split.single-split > div:nth-child(2) {
overflow: visible !important; /* Ensure buttons remain visible */
Expand Down
31 changes: 26 additions & 5 deletions src/panel_splitjs/models/split.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,32 @@ export function render({ model, el }) {
split_div.className = `split single-split ${model.orientation}`
split_div.classList.add("loading")

const [left_min, right_min] = Array.isArray(model.min_size) ? model.min_size : [model.min_size, model.min_size]

if (model.orientation === "horizontal") {
split_div.style.minWidth = `${left_min + right_min + 8}px`
} else {
split_div.style.minHeight = `${left_min + right_min + 8}px`
}

const split0 = document.createElement("div")
split0.className = "split-panel"
if (left_min) {
if (model.orientation === "horizontal") {
split0.style.minWidth = `${left_min}px`
} else {
split0.style.minHeight = `${left_min}px`
}
}
const split1 = document.createElement("div")
split1.className = "split-panel"
if (right_min) {
if (model.orientation === "horizontal") {
split1.style.minWidth = `${right_min}px`
} else {
split1.style.minHeight = `${right_min}px`
}
}
split_div.append(split0, split1)

const left_content_wrapper = document.createElement("div")
Expand Down Expand Up @@ -63,7 +85,6 @@ export function render({ model, el }) {
new_sizes = [0, 100]
left_click_count = 0
}
model.collapsed = is_collapsed
sync_ui(new_sizes, true)
})

Expand All @@ -80,7 +101,6 @@ export function render({ model, el }) {
new_sizes = [100, 0]
right_click_count = 0
}
model.collapsed = is_collapsed
sync_ui(new_sizes, true)
})
}
Expand Down Expand Up @@ -123,8 +143,8 @@ export function render({ model, el }) {
})

function sync_ui(sizes = null, resize = false) {
const left_panel_hidden = sizes ? sizes[0] <= COLLAPSED_SIZE : false
const right_panel_hidden = sizes ? sizes[1] <= COLLAPSED_SIZE : false
const left_panel_hidden = sizes ? ((sizes[0] <= COLLAPSED_SIZE) && (left_min < COLLAPSED_SIZE)) : false
const right_panel_hidden = sizes ? ((sizes[1] <= COLLAPSED_SIZE) && (right_min < COLLAPSED_SIZE)) : false

let [ls, rs] = sizes
if (right_panel_hidden) {
Expand All @@ -143,8 +163,8 @@ export function render({ model, el }) {
if (resize) {
split_instance.setSizes([ls, rs])
sizes = [ls, rs]
model.sizes = [ls, rs]
window.dispatchEvent(new Event('resize'))
requestAnimationFrame(() => { model.sizes = split_instance.getSizes() })
}
}

Expand All @@ -153,6 +173,7 @@ export function render({ model, el }) {
return
}
sizes = model.sizes
model.collapsed = (1-sizes[0]) >= 0 ? 0 : (1-sizes[1]) >= 0 ? 1 : null
sync_ui(sizes, true)
})

Expand Down
72 changes: 68 additions & 4 deletions tests/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pytest.importorskip('playwright')

from panel.tests.util import serve_component, wait_until
from panel.pane import Markdown
from panel.widgets import Button
from panel_splitjs import MultiSplit, Split
from playwright.sync_api import expect
Expand All @@ -26,6 +27,69 @@ def test_split(page, orientation):
expect(page.locator('.split-panel').last).to_have_attribute('style', f'{attr}: calc(50% - 4px);')


@pytest.mark.parametrize('orientation', ['horizontal', 'vertical'])
def test_split_min_size_and_total_width(page, orientation):
split = Split(
Markdown("## Left Panel\nContent here", width=150, margin=25),
Markdown("## Right Panel\nMore content", width=150, margin=25),
sizes=(50, 50), # Equal sizing initially
min_size=300, # Minimum 300px for each panel
show_buttons=True,
orientation=orientation
)
serve_component(page, split)

# Check total width/height is 608px
attr = "width" if orientation == "horizontal" else "height"
expect(page.locator(".single-split")).to_have_attribute("style", f"min-{attr}: 608px;")

# Collapse left panel using the left button
left_button = page.locator(".toggle-button-left,.toggle-button-up").first
left_button.click()
# Wait to ensure UI has updated; the left panel should not collapse below min_size=300px,
# so after collapse, its width/height should be 300px.
expect(page.locator(".split-panel").first).to_have_css(attr, "300px")

# Collapse right panel using the right button
right_button = page.locator(".toggle-button-right,.toggle-button-down").first
right_button.click()
# Wait, then check right does not collapse below 300px
expect(page.locator(".split-panel").last).to_have_css(attr, "300px")

@pytest.mark.parametrize('orientation', ['horizontal', 'vertical'])
def test_split_min_size_one_side(page, orientation):
# Only first panel has a min_size of 300px, second is unconstrained
split = Split(
Markdown("## Left", width=150),
Markdown("## Right", width=150),
sizes=(50, 50),
min_size=(300, None),
show_buttons=True,
orientation=orientation,
width=600 if orientation == "horizontal" else None,
height=600 if orientation == "vertical" else None
)
serve_component(page, split)

attr = "width" if orientation == "horizontal" else "height"
# The minimum size style is still 308px (because None becomes 0, so 300+0+8)
expect(page.locator(".single-split")).to_have_attribute("style", f"min-{attr}: 308px;")

# Collapse left panel -- it should not collapse below 300px
left_button = page.locator(".toggle-button-left,.toggle-button-up").first
left_button.click()
expect(page.locator(".split-panel").first).to_have_css(attr, "300px")

# Collapse right panel -- right can collapse to minimum, which is 5px (default collapsed size)
right_button = page.locator(".toggle-button-right,.toggle-button-down").first
right_button.click()
# The right panel should be collapsed to zero
expect(page.locator(".split-panel").last).to_have_css(attr, "0px")

# The left panel still stays at its min_size
expect(page.locator(".split-panel").first).to_have_css(attr, "592px")


def test_split_sizes(page):
split = Split(Button(name='Left'), Button(name='Right'), sizes=(40, 60), width=400)
serve_component(page, split)
Expand Down Expand Up @@ -64,13 +128,13 @@ def test_split_collapsed_programmatically(page):
split.collapsed = 0
expect(page.locator('.split-panel').first).to_have_attribute('style', 'width: calc(1% - 4px);')
expect(page.locator('.split-panel').last).to_have_attribute('style', 'width: calc(99% - 4px);')
wait_until(lambda: split.sizes == (0, 100), page)
wait_until(lambda: split.sizes == (1, 99), page)
wait_until(lambda: split.collapsed == 0, page)

split.collapsed = 1
expect(page.locator('.split-panel').first).to_have_attribute('style', 'width: calc(99% - 4px);')
expect(page.locator('.split-panel').last).to_have_attribute('style', 'width: calc(1% - 4px);')
wait_until(lambda: split.sizes == (100, 0), page)
wait_until(lambda: split.sizes == (99, 1), page)
wait_until(lambda: split.collapsed == 1, page)

split.collapsed = None
Expand Down Expand Up @@ -104,7 +168,7 @@ def test_split_click_toggle_button(page, orientation):
page.locator(f'.toggle-button-{btn1}').click()
expect(page.locator('.split-panel').first).to_have_attribute('style', f'{attr}: calc(1% - 4px);')
expect(page.locator('.split-panel').last).to_have_attribute('style', f'{attr}: calc(99% - 4px);')
wait_until(lambda: split.sizes == (0, 100), page)
wait_until(lambda: split.sizes == (1, 99), page)
wait_until(lambda: split.collapsed == 0, page)

page.locator(f'.toggle-button-{btn2}').click()
Expand All @@ -116,7 +180,7 @@ def test_split_click_toggle_button(page, orientation):
page.locator(f'.toggle-button-{btn2}').click()
expect(page.locator('.split-panel').first).to_have_attribute('style', f'{attr}: calc(99% - 4px);')
expect(page.locator('.split-panel').last).to_have_attribute('style', f'{attr}: calc(1% - 4px);')
wait_until(lambda: split.sizes == (100, 0), page)
wait_until(lambda: split.sizes == (99, 1), page)
wait_until(lambda: split.collapsed == 1, page)


Expand Down