Skip to content

Commit aca86fe

Browse files
authored
Ensure child objects are correctly re-rendered (#9)
1 parent 9ed590f commit aca86fe

File tree

6 files changed

+138
-37
lines changed

6 files changed

+138
-37
lines changed

examples/apps/multi.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
from panel_splitjs import MultiSplit
2-
from panel_material_ui import Paper
2+
from panel_material_ui import Button, Column, Paper, Row
33

44
paper_opts = dict(elevation=3, margin=10, sizing_mode="stretch_both")
55

6-
MultiSplit(
7-
objects=[
8-
Paper("Foo", **paper_opts),
9-
Paper("Bar", **paper_opts),
10-
Paper("Baz", **paper_opts),
11-
Paper("Qux", **paper_opts),
12-
Paper("Quux", **paper_opts),
13-
],
6+
add = Button(label="Add item", on_click=lambda _: ms.append(Paper("Added", **paper_opts)))
7+
insert = Button(label="Insert item", on_click=lambda _: ms.insert(2, Paper("Inserted", **paper_opts)))
8+
9+
ms = MultiSplit(
10+
Paper("Foo", **paper_opts),
11+
Paper("Bar", **paper_opts),
12+
Paper("Baz", **paper_opts),
13+
Paper("Qux", **paper_opts),
14+
Paper("Quux", **paper_opts),
1415
sizes=(20, 30, 20, 10, 20),
1516
min_size=100,
1617
sizing_mode="stretch_width",
1718
height=400
18-
).servable()
19+
)
20+
21+
Column(Row(add, insert), ms).servable()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ classifiers = [
4141
]
4242

4343
dependencies = [
44-
'panel >=1.8.0',
44+
'panel >=1.8.3',
4545
'packaging',
4646
]
4747

src/panel_splitjs/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class SplitBase(JSComponent, ListLike):
5555

5656
_bundle = DIST_PATH / "panel-splitjs.bundle.js"
5757
_stylesheets = [DIST_PATH / "css" / "splitjs.css"]
58+
_render_policy = "manual"
5859

5960
__abstract = True
6061

src/panel_splitjs/dist/css/splitjs.css

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@
126126
z-index: 1;
127127
}
128128

129-
.gutter.gutter-horizontal {
129+
.gutter.gutter-vertical {
130130
cursor: row-resize;
131131
}
132132

@@ -146,20 +146,53 @@
146146
.gutter.gutter-horizontal::after {
147147
width: 10px;
148148
height: 24px;
149+
cursor: col-resize;
149150
}
150151

151152
.gutter.gutter-vertical::after {
152153
width: 24px;
153154
height: 10px;
154155
-webkit-mask-image: url("handle_vertical.svg");
155156
mask-image: url("handle_vertical.svg");
157+
cursor: row-resize;
158+
}
159+
160+
.gutter > .divider {
161+
--gap: 40px;
162+
--thickness: 1px;
163+
--color: var(--border-color);
164+
background: var(--color);
165+
position: absolute;
156166
}
157167

158-
.split > div:nth-child(2) > div:not(.toggle-button-left):not(.toggle-button-right):not(.toggle-button-up):not(.toggle-button-down) {
168+
/* Horizontal variant */
169+
.gutter-vertical > .divider {
170+
top: 50%;
159171
width: 100%;
172+
height: var(--thickness);
173+
background:
174+
linear-gradient(to right,
175+
var(--color) 0,
176+
var(--color) calc(50% - var(--gap) / 2),
177+
transparent calc(50% - var(--gap) / 2),
178+
transparent calc(50% + var(--gap) / 2),
179+
var(--color) calc(50% + var(--gap) / 2),
180+
var(--color) 100%);
181+
}
182+
183+
/* Vertical variant */
184+
.gutter-horizontal > .divider {
185+
left: 50%;
160186
height: 100%;
161-
overflow: auto;
162-
padding-top: 36px; /* Space for the toggle buttons */
187+
width: var(--thickness);
188+
background:
189+
linear-gradient(to bottom,
190+
var(--color) 0,
191+
var(--color) calc(50% - var(--gap) / 2),
192+
transparent calc(50% - var(--gap) / 2),
193+
transparent calc(50% + var(--gap) / 2),
194+
var(--color) calc(50% + var(--gap) / 2),
195+
var(--color) 100%);
163196
}
164197

165198
/* Toggle button base styles */

src/panel_splitjs/models/multi_split.js

Lines changed: 72 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,83 @@ export function render({ model, el }) {
55
split_div.className = `split multi-split ${model.orientation}`
66
split_div.classList.add("loading")
77

8-
const objects = model.objects ? model.get_child("objects") : []
9-
const split_items = []
10-
for (let i = 0; i < objects.length; i++) {
11-
const split_item = document.createElement("div")
12-
split_item.className = "split-panel"
13-
split_div.append(split_item)
14-
split_items.push(split_item)
15-
split_item.append(objects[i])
8+
let split = null
9+
10+
function reconcileChildren(parent, desiredChildren) {
11+
// Ensure each desired child is at the correct index
12+
for (let i = 0; i < desiredChildren.length; i++) {
13+
const child = desiredChildren[i]
14+
const current = parent.children[i]
15+
if (current?.id === child.id) continue
16+
if (current) {
17+
parent.insertBefore(child, current)
18+
} else {
19+
parent.append(child)
20+
}
21+
}
22+
23+
// Remove any extra children that are no longer desired
24+
while (parent.children.length > desiredChildren.length) {
25+
parent.removeChild(parent.lastElementChild)
26+
}
1627
}
1728

18-
el.append(split_div)
29+
const render_splits = () => {
30+
if (split != null) {
31+
split.destroy()
32+
split = null
33+
}
34+
35+
const objects = model.objects ? model.get_child("objects") : []
36+
const split_items = []
37+
38+
for (let i = 0; i < objects.length; i++) {
39+
const obj = objects[i]
40+
const id = `split-panel-${model.objects[i].id}`
1941

20-
let sizes = model.sizes
21-
const split = Split(split_items, {
22-
sizes: sizes,
23-
minSize: model.min_size || 0,
24-
maxSize: model.max_size || Number("Infinity"),
25-
dragInterval: model.step_size || 1,
26-
snapOffset: model.snap_size || 30,
27-
gutterSize: 8,
28-
direction: model.orientation,
29-
onDragEnd: (new_sizes) => {
30-
sizes = new_sizes
31-
this.model.sizes = sizes
42+
// Try to reuse an existing split_item
43+
let split_item = el.querySelector(`#${id}`)
44+
if (split_item == null) {
45+
split_item = document.createElement("div")
46+
split_item.className = "split-panel"
47+
split_item.id = id
48+
split_item.replaceChildren(obj)
49+
}
50+
51+
split_items.push(split_item)
3252
}
33-
})
3453

54+
// Incrementally reorder / trim children of split_div
55+
reconcileChildren(split_div, split_items)
56+
57+
let sizes = model.sizes
58+
split = Split(split_items, {
59+
sizes,
60+
minSize: model.min_size || 0,
61+
maxSize: model.max_size || Number("Infinity"),
62+
dragInterval: model.step_size || 1,
63+
snapOffset: model.snap_size || 30,
64+
gutterSize: 8,
65+
gutter: (index, direction) => {
66+
const gutter = document.createElement('div')
67+
gutter.className = `gutter gutter-${direction}`
68+
const divider = document.createElement('div')
69+
divider.className = "divider"
70+
gutter.append(divider)
71+
return gutter
72+
},
73+
direction: model.orientation,
74+
onDragEnd: (new_sizes) => {
75+
sizes = new_sizes
76+
this.model.sizes = sizes
77+
}
78+
})
79+
}
80+
81+
render_splits()
82+
el.append(split_div)
83+
84+
model.on("objects", render_splits)
3585
model.on("sizes", () => {
3686
if (sizes === model.sizes) {
3787
return

src/panel_splitjs/models/split.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ export function render({ model, el }) {
2626
split0.append(left_content_wrapper)
2727
split1.append(right_content_wrapper)
2828

29+
model.on("objects", () => {
30+
const [left, right] = model.get_child("objects")
31+
left_content_wrapper.replaceChildren(left)
32+
right_content_wrapper.replaceChildren(right)
33+
})
34+
2935
let left_arrow_button, right_arrow_button
3036
let left_click_count = 0
3137
let right_click_count = 0
@@ -91,6 +97,14 @@ export function render({ model, el }) {
9197
dragInterval: model.step_size,
9298
snapOffset: model.snap_size,
9399
gutterSize: 8,
100+
gutter: (index, direction) => {
101+
const gutter = document.createElement('div')
102+
gutter.className = `gutter gutter-${direction}`
103+
const divider = document.createElement('div')
104+
divider.className = "divider"
105+
gutter.append(divider)
106+
return gutter
107+
},
94108
direction: model.orientation,
95109
onDrag: (sizes) => {
96110
const new_collapsed_state = sizes[0] <= COLLAPSED_SIZE ? 0 : (sizes[1] <= COLLAPSED_SIZE ? 1 : null)

0 commit comments

Comments
 (0)