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
5 changes: 4 additions & 1 deletion src/panel_splitjs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def _validate(self, val):

class SplitBase(JSComponent, ListLike):

gutter_size = param.Integer(default=8, doc="""
Width of the gutter element.""")

max_size = Size(default=None, doc="""
The maximum sizes of the panels (in pixels) either as a single value or a tuple.""")

Expand Down Expand Up @@ -109,7 +112,7 @@ class Split(SplitBase):
The component to place in the left panel.
When invert=True, this will appear on the right side.""")

show_buttons = param.Boolean(default=True, doc="""
show_buttons = param.Boolean(default=False, doc="""
Whether to show the toggle buttons on the divider.
When False, the buttons are hidden and panels can only be resized by dragging.""")

Expand Down
145 changes: 46 additions & 99 deletions src/panel_splitjs/dist/css/splitjs.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,27 @@
flex-direction: column;
}

/* Padding for panel edges unless collapsed */
.single-split.horizontal.expand-buttons > div:nth-child(1) { padding-right: 15px; }
.single-split.horizontal > div:nth-child(1):has(.collapsed-content) { padding-right: 0; }
.single-split.horizontal.expand-buttons > div:nth-child(3) { padding-left: 15px; }
.single-split.horizontal > div:nth-child(3):has(.collapsed-content) { padding-left: 0; }

.single-split.vertical.expand-buttons > div:nth-child(1) { padding-bottom: 15px; }
.single-split.vertical > div:nth-child(1):has(.collapsed-content) { padding-bottom: 0; }
.single-split.vertical.expand-buttons > div:nth-child(3) { padding-top: 15px; }
.single-split.vertical > div:nth-child(3):has(.collapsed-content) { padding-top: 0; }

/* Style for initial load to prevent FOUC */
.loading {
visibility: hidden;
}

/* Max width for comfortable reading */
.left-panel-content {
max-width: clamp(450px, 95vw, 1200px);
margin: 0px auto;
}

/* Split panel styles */
/* Split panel styles (combine .split > div rules) */
.split > div {
position: relative;
}

.split.single-split > div:nth-child(1) {
overflow: clip;
}

.split.single-split > div:nth-child(2) {
overflow: visible;
position: relative;
width: 100%;
}


.split.single-split > div:nth-child(2) {
overflow: visible !important; /* Ensure buttons remain visible */
position: relative !important;
}

/* Content wrapper styles */
.content-wrapper {
width: 100%;
Expand All @@ -54,7 +43,7 @@
padding-inline: 0.5rem;
}

/* Toggle button basic styles */
/* Toggle button base styles (deduplicated, combine base and arrow masks) */
.toggle-button-left, .toggle-button-right, .toggle-button-up, .toggle-button-down {
position: absolute;
width: 24px;
Expand All @@ -68,34 +57,47 @@
transition: opacity 0.2s;
border-radius: 4px;
padding: 2px;

background-color: var(--panel-on-background-color);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: 16px;
mask-size: 16px;
}

/* Left button (<) - positioned on left side of divider */
.toggle-button-left {
left: -34px; /* 24px width + 10px spacing from divider */
left: -28px;
top: 50%;
transform: translateY(-50%);
-webkit-mask-image: url("arrow_left.svg");
mask-image: url("arrow_left.svg");
}

/* Right button (>) - positioned on right side of divider */
.toggle-button-right {
left: 2px;
left: -4px;
top: 50%;
transform: translateY(-50%);
-webkit-mask-image: url("arrow_right.svg");
mask-image: url("arrow_right.svg");
}

/* Up button (^) - positioned above divider */
.toggle-button-up {
top: -34px; /* 24px height + 10px spacing from divider */
top: -28px;
left: 50%;
transform: translateX(-50%);
-webkit-mask-image: url("arrow_up.svg");
mask-image: url("arrow_up.svg");
}

/* Down button (v) - positioned below divider */
.toggle-button-down {
top: 2px;
top: -4px;
left: 50%;
transform: translateX(-50%);
-webkit-mask-image: url("arrow_down.svg");
mask-image: url("arrow_down.svg");
}

.toggle-button-left:hover, .toggle-button-right:hover, .toggle-button-up:hover, .toggle-button-down:hover {
opacity: 1;
}

/* Collapsed state */
Expand All @@ -109,23 +111,19 @@
background-repeat: no-repeat;
background-position: 50%;
transition: background-color 0.2s;
z-index: 999;
}

.gutter:hover {
background-color: var(--panel-surface-color);
}

.gutter.gutter-horizontal {
cursor: col-resize;
position: relative;
width: 10px;
z-index: 1;
}

.gutter.gutter-vertical {
cursor: row-resize;
}

.gutter::after {
content: "";
position: absolute;
Expand All @@ -138,33 +136,30 @@
mask-repeat: no-repeat;
background-color: var(--panel-border-color);
}

.gutter.gutter-horizontal::after {
width: 10px;
height: 24px;
cursor: col-resize;
}

.gutter.gutter-vertical::after {
width: 24px;
height: 10px;
-webkit-mask-image: url("handle_vertical.svg");
mask-image: url("handle_vertical.svg");
cursor: row-resize;
}

.gutter > .divider {
--gap: 40px;
--thickness: 1px;
--color: var(--border-color);
--color: var(--panel-border-color);
background: var(--color);
position: absolute;
}

/* Horizontal variant */
/* Horizontal variant for divider */
.gutter-vertical > .divider {
top: 50%;
width: 100%;
left: 2.5%;
width: 95%;
height: var(--thickness);
background:
linear-gradient(to right,
Expand All @@ -175,11 +170,11 @@
var(--color) calc(50% + var(--gap) / 2),
var(--color) 100%);
}

/* Vertical variant */
/* Vertical variant for divider */
.gutter-horizontal > .divider {
left: 50%;
height: 100%;
top: 2.5%;
height: 95%;
width: var(--thickness);
background:
linear-gradient(to bottom,
Expand All @@ -191,73 +186,25 @@
var(--color) 100%);
}

/* Toggle button base styles */
.toggle-button-left,
.toggle-button-right,
.toggle-button-up,
.toggle-button-down {
width: 24px;
height: 24px;
background-color: var(--panel-on-background-color);
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: 16px;
mask-size: 16px;
opacity: 0.5;
}

.toggle-button-left:hover, .toggle-button-right:hover, .toggle-button-up:hover, .toggle-button-down:hover {
opacity: 1;
}

/* Left arrow button */
.toggle-button-left {
-webkit-mask-image: url("arrow_left.svg");
mask-image: url("arrow_left.svg");
}

/* Right arrow button */
.toggle-button-right {
-webkit-mask-image: url("arrow_right.svg");
mask-image: url("arrow_right.svg");
}

/* Up arrow button */
.toggle-button-up {
-webkit-mask-image: url("arrow_up.svg");
mask-image: url("arrow_up.svg");
}

/* Down arrow button */
.toggle-button-down {
-webkit-mask-image: url("arrow_down.svg");
mask-image: url("arrow_down.svg");
}

/* Animation for toggle icon */
/* Toggle button animation */
@keyframes jumpLeftRight {
0%, 100% { transform: translateY(-50%); }
25% { transform: translate(-4px, -50%); }
50% { transform: translateY(-50%); }
75% { transform: translate(4px, -50%); }
}

@keyframes jumpUpDown {
0%, 100% { transform: translateX(-50%); }
25% { transform: translate(-50%, -4px); }
50% { transform: translateX(-50%); }
75% { transform: translate(-50%, 4px); }
}

.toggle-button-left.animated, .toggle-button-right.animated {
animation-name: jumpLeftRight;
animation-duration: 0.5s;
animation-timing-function: ease;
animation-iteration-count: 3;
}

.toggle-button-up.animated, .toggle-button-down.animated {
animation-name: jumpUpDown;
animation-duration: 0.5s;
Expand Down
4 changes: 3 additions & 1 deletion src/panel_splitjs/models/multi_split.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Split from "https://esm.sh/[email protected]"
export function render({ model, el }) {
const split_div = document.createElement("div")
split_div.className = `split multi-split ${model.orientation}`
split_div.style.visibility = "hidden"
split_div.classList.add("loading")

let split = null
Expand Down Expand Up @@ -61,7 +62,7 @@ export function render({ model, el }) {
maxSize: model.max_size || Number("Infinity"),
dragInterval: model.step_size || 1,
snapOffset: model.snap_size || 30,
gutterSize: 8,
gutterSize: model.gutter_size,
gutter: (index, direction) => {
const gutter = document.createElement('div')
gutter.className = `gutter gutter-${direction}`
Expand Down Expand Up @@ -94,6 +95,7 @@ export function render({ model, el }) {
model.on("after_layout", () => {
if (!initialized) {
initialized = true
split_div.style.visibility = ""
split_div.classList.remove("loading")
}
})
Expand Down
13 changes: 9 additions & 4 deletions src/panel_splitjs/models/split.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ const COLLAPSED_SIZE = 5

export function render({ model, el }) {
const split_div = document.createElement("div")
split_div.className = `split single-split ${model.orientation}`
split_div.className = `split single-split ${model.orientation} `
split_div.style.visibility = "hidden"
split_div.classList.add("loading")
if (model.show_buttons) {
split_div.classList.add("expand-buttons")
}

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`
split_div.style.minWidth = `${left_min + right_min + model.gutter_size}px`
} else {
split_div.style.minHeight = `${left_min + right_min + 8}px`
split_div.style.minHeight = `${left_min + right_min + model.gutter_size}px`
}

const split0 = document.createElement("div")
Expand Down Expand Up @@ -116,7 +120,7 @@ export function render({ model, el }) {
maxSize: model.max_size || Number("Infinity"),
dragInterval: model.step_size,
snapOffset: model.snap_size,
gutterSize: 8,
gutterSize: model.gutter_size,
gutter: (index, direction) => {
const gutter = document.createElement('div')
gutter.className = `gutter gutter-${direction}`
Expand Down Expand Up @@ -204,6 +208,7 @@ export function render({ model, el }) {
}, 1500)
}
window.dispatchEvent(new Event('resize'))
split_div.style.visibility = ""
split_div.classList.remove("loading")
})

Expand Down
2 changes: 1 addition & 1 deletion tests/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def test_split_sizes_programmatically(page):
@pytest.mark.parametrize('orientation', ['horizontal', 'vertical'])
def test_split_click_toggle_button(page, orientation):
kwargs = {'width': 400} if orientation == 'horizontal' else {'height': 400}
split = Split(Button(name='Left'), Button(name='Right'), orientation=orientation, **kwargs)
split = Split(Button(name='Left'), Button(name='Right'), orientation=orientation, show_buttons=True, **kwargs)
serve_component(page, split)

btn1, btn2 = ("left", "right") if orientation == "horizontal" else ("up", "down")
Expand Down