panel-splitjs 0.0.1a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ from .__version import __version__ # noqa
2
+ from .base import HSplit, Split, VSplit
3
+
4
+ __all__ = ["HSplit", "Split", "VSplit"]
@@ -0,0 +1,46 @@
1
+ """Define the package version.
2
+
3
+ Called __version.py as setuptools_scm will create a _version.py
4
+ """
5
+
6
+ import pathlib
7
+
8
+ PACKAGE = "panel_splitjs"
9
+
10
+ try:
11
+ # For performance reasons on imports, avoid importing setuptools_scm
12
+ # if not in a .git folder
13
+ if (pathlib.Path(__file__).parent.parent.parent / ".git").is_dir():
14
+ # If setuptools_scm is installed (e.g. in a development environment with
15
+ # an editable install), then use it to determine the version dynamically.
16
+ from setuptools_scm import get_version
17
+
18
+ # This will fail with LookupError if the package is not installed in
19
+ # editable mode or if Git is not installed.
20
+ __version__ = get_version(
21
+ root="../..", relative_to=__file__, version_scheme="post-release"
22
+ )
23
+ else:
24
+ raise FileNotFoundError
25
+ except (ImportError, LookupError, FileNotFoundError):
26
+ # As a fallback, use the version that is hard-coded in the file.
27
+ try:
28
+ # __version__ was added in _version in setuptools-scm 7.0.0, we rely on
29
+ # the hopefully stable version variable.
30
+ from ._version import version as __version__ # type: ignore
31
+ except (ModuleNotFoundError, ImportError):
32
+ # Either _version doesn't exist (ModuleNotFoundError) or version isn't
33
+ # in _version (ImportError). ModuleNotFoundError is a subclass of
34
+ # ImportError, let's be explicit anyway.
35
+
36
+ # Try something else:
37
+ from importlib.metadata import PackageNotFoundError, version
38
+
39
+ try:
40
+ __version__ = version(PACKAGE)
41
+ except PackageNotFoundError:
42
+ # The user is probably trying to run this without having installed
43
+ # the package.
44
+ __version__ = "0.0.0+unknown"
45
+
46
+ __all__ = ("__version__",)
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.0.1a0'
32
+ __version_tuple__ = version_tuple = (0, 0, 1, 'a0')
33
+
34
+ __commit_id__ = commit_id = None
panel_splitjs/base.py ADDED
@@ -0,0 +1,139 @@
1
+ from pathlib import Path
2
+
3
+ import param
4
+ from bokeh.embed.bundle import extension_dirs
5
+ from panel.custom import Children, JSComponent
6
+ from panel.layout import Spacer
7
+ from panel.layout.base import ListLike
8
+
9
+ BASE_PATH = Path(__file__).parent
10
+ DIST_PATH = BASE_PATH / 'dist'
11
+
12
+ extension_dirs['panel-splitjs'] = DIST_PATH
13
+
14
+
15
+ class SplitChildren(Children):
16
+ """A Children parameter that only allows at most two items."""
17
+
18
+ def _transform_value(self, val):
19
+ if val is param.parameterized.Undefined:
20
+ return [Spacer(), Spacer()]
21
+ if any(v is None for v in val):
22
+ val[:] = [Spacer() if v is None else v for v in val]
23
+ if len(val) == 1:
24
+ val.append(Spacer())
25
+ if len(val) == 0:
26
+ val.extend([Spacer(), Spacer()])
27
+ val = super()._transform_value(val)
28
+ return val
29
+
30
+ def _validate(self, val):
31
+ super()._validate(val)
32
+ if len(val) <= 2:
33
+ return
34
+ if self.owner is None:
35
+ objtype = ""
36
+ elif isinstance(self.owner, param.Parameterized):
37
+ objtype = self.owner.__class__.__name__
38
+ else:
39
+ objtype = self.owner.__name__
40
+ raise ValueError(f"{objtype} component must have at most two children.")
41
+
42
+
43
+ class Split(JSComponent, ListLike):
44
+ """
45
+ Split is a component for creating a responsive split panel layout.
46
+
47
+ This component uses split.js to create a draggable split layout with two panels.
48
+
49
+ Key features include:
50
+ - Collapsible panels with toggle button
51
+ - Minimum size constraints for each panel
52
+ - Invertible layout to support different UI configurations
53
+ - Responsive sizing with automatic adjustments
54
+ - Animation for better user experience
55
+
56
+ The component is ideal for creating application layouts with a main content area
57
+ and a secondary panel that can be toggled (like a chat interface with output display).
58
+ """
59
+
60
+ collapsed = param.Boolean(default=False, doc="""
61
+ Whether the secondary panel is collapsed.
62
+ When True, only one panel is visible (determined by invert).
63
+ When False, both panels are visible according to expanded_sizes.""")
64
+
65
+ expanded_sizes = param.NumericTuple(default=(50, 50), length=2, doc="""
66
+ The sizes of the two panels when expanded (as percentages).
67
+ Default is (50, 50) which means the left panel takes up 35% of the space
68
+ and the right panel takes up 65% when expanded.
69
+ When invert=True, these percentages are automatically swapped.""")
70
+
71
+ invert = param.Boolean(default=False, constant=True, doc="""
72
+ Whether to invert the layout, changing the toggle button side and panel styles.""")
73
+
74
+ min_sizes = param.NumericTuple(default=(0, 0), length=2, doc="""
75
+ The minimum sizes of the two panels (in pixels).
76
+ Default is (0, 0) which allows both panels to fully collapse.
77
+ Set to (300, 0) or similar values if you want to enforce minimum widths during dragging.
78
+ When invert=True, these values are automatically swapped.""")
79
+
80
+ objects = SplitChildren(doc="""
81
+ The component to place in the left panel.
82
+ When invert=True, this will appear on the right side.""")
83
+
84
+ orientation = param.Selector(default="horizontal", objects=["horizontal", "vertical"], doc="""
85
+ The orientation of the split panel. Default is horizontal.""")
86
+
87
+ show_buttons = param.Boolean(default=False, doc="""
88
+ Whether to show the toggle buttons on the divider.
89
+ When False, the buttons are hidden and panels can only be resized by dragging.""")
90
+
91
+ sizes = param.NumericTuple(default=(100, 0), length=2, doc="""
92
+ The initial sizes of the two panels (as percentages).
93
+ Default is (100, 0) which means the left panel takes up all the space
94
+ and the right panel is not visible.""")
95
+
96
+ _bundle = DIST_PATH / "panel-splitjs.bundle.js"
97
+ _esm = Path(__file__).parent / "models" / "splitjs.js"
98
+
99
+ _stylesheets = [DIST_PATH / "css" / "splitjs.css"]
100
+
101
+ def __init__(self, *objects, **params):
102
+ if objects:
103
+ params["objects"] = list(objects)
104
+ super().__init__(**params)
105
+ if self.invert:
106
+ # Swap min_sizes when inverted
107
+ left_min, right_min = self.min_sizes
108
+ self.min_sizes = (right_min, left_min)
109
+
110
+ # Swap expanded_sizes when inverted
111
+ left_exp, right_exp = self.expanded_sizes
112
+ self.expanded_sizes = (right_exp, left_exp)
113
+
114
+ @param.depends("collapsed", watch=True)
115
+ def _send_collapsed_update(self):
116
+ """Send message to JS when collapsed state changes in Python"""
117
+ self._send_msg({"type": "update_collapsed", "collapsed": self.collapsed})
118
+
119
+ def _handle_msg(self, msg):
120
+ """Handle messages from JS"""
121
+ if 'collapsed' in msg:
122
+ collapsed = msg['collapsed']
123
+ with param.discard_events(self):
124
+ # Important to discard so when user drags the panel, it doesn't
125
+ # expand to the expanded sizes
126
+ self.collapsed = collapsed
127
+
128
+
129
+ class HSplit(Split):
130
+
131
+ orientation = param.Selector(default="horizontal", objects=["horizontal"], readonly=True)
132
+
133
+
134
+ class VSplit(Split):
135
+
136
+ orientation = param.Selector(default="vertical", objects=["vertical"], readonly=True)
137
+
138
+
139
+ __all__ = ["HSplit", "Split", "VSplit"]
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <polyline points="6 9 12 15 18 9"></polyline>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <polyline points="15 18 9 12 15 6"></polyline>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <polyline points="9 6 15 12 9 18"></polyline>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <polyline points="6 15 12 9 18 15"></polyline>
3
+ </svg>
@@ -0,0 +1,23 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="10"
4
+ height="24"
5
+ viewBox="0 0 10 24"
6
+ fill="currentColor"
7
+ aria-hidden="true"
8
+ >
9
+ <rect x="2.5" y="2" width="2" height="2" rx="0" />
10
+ <rect x="2.5" y="5" width="2" height="2" rx="0" />
11
+ <rect x="2.5" y="8" width="2" height="2" rx="0" />
12
+ <rect x="2.5" y="11" width="2" height="2" rx="0" />
13
+ <rect x="2.5" y="14" width="2" height="2" rx="0" />
14
+ <rect x="2.5" y="17" width="2" height="2" rx="0" />
15
+ <rect x="2.5" y="20" width="2" height="2" rx="0" />
16
+ <rect x="5.5" y="2" width="2" height="2" rx="0" />
17
+ <rect x="5.5" y="5" width="2" height="2" rx="0" />
18
+ <rect x="5.5" y="8" width="2" height="2" rx="0" />
19
+ <rect x="5.5" y="11" width="2" height="2" rx="0" />
20
+ <rect x="5.5" y="14" width="2" height="2" rx="0" />
21
+ <rect x="5.5" y="17" width="2" height="2" rx="0" />
22
+ <rect x="5.5" y="20" width="2" height="2" rx="0" />
23
+ </svg>
@@ -0,0 +1,23 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ width="24"
4
+ height="10"
5
+ viewBox="0 0 24 10"
6
+ fill="currentColor"
7
+ aria-hidden="true"
8
+ >
9
+ <rect x="2" y="2.5" width="2" height="2" />
10
+ <rect x="5" y="2.5" width="2" height="2" />
11
+ <rect x="8" y="2.5" width="2" height="2" />
12
+ <rect x="11" y="2.5" width="2" height="2" />
13
+ <rect x="14" y="2.5" width="2" height="2" />
14
+ <rect x="17" y="2.5" width="2" height="2" />
15
+ <rect x="20" y="2.5" width="2" height="2" />
16
+ <rect x="2" y="5.5" width="2" height="2" />
17
+ <rect x="5" y="5.5" width="2" height="2" />
18
+ <rect x="8" y="5.5" width="2" height="2" />
19
+ <rect x="11" y="5.5" width="2" height="2" />
20
+ <rect x="14" y="5.5" width="2" height="2" />
21
+ <rect x="17" y="5.5" width="2" height="2" />
22
+ <rect x="20" y="5.5" width="2" height="2" />
23
+ </svg>
@@ -0,0 +1,239 @@
1
+ .split {
2
+ display: flex;
3
+ height: 100%;
4
+ width: 100%;
5
+ overflow: clip; /* Clip overflow to prevent scrollbars; inner content will have their own */
6
+ }
7
+
8
+ .split.horizontal {
9
+ flex-direction: row;
10
+ }
11
+
12
+ .split.vertical {
13
+ flex-direction: column;
14
+ }
15
+
16
+ /* Style for initial load to prevent FOUC */
17
+ .split.loading {
18
+ visibility: hidden;
19
+ }
20
+
21
+ /* Max width for comfortable reading */
22
+ .left-panel-content {
23
+ max-width: clamp(450px, 95vw, 1200px);
24
+ margin: 0px auto;
25
+ }
26
+
27
+ /* Split panel styles */
28
+ .split > div {
29
+ position: relative;
30
+ }
31
+
32
+ .split > div:nth-child(2) {
33
+ overflow: visible;
34
+ position: relative;
35
+ width: 100%;
36
+ }
37
+
38
+ /* Content wrapper styles */
39
+ .content-wrapper {
40
+ width: 100%;
41
+ height: 100%;
42
+ display: contents;
43
+ box-sizing: border-box;
44
+ padding-inline: 0.5rem;
45
+ }
46
+
47
+ /* Toggle button basic styles */
48
+ .toggle-button-left, .toggle-button-right, .toggle-button-up, .toggle-button-down {
49
+ position: absolute;
50
+ width: 24px;
51
+ height: 24px;
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ cursor: pointer;
56
+ z-index: 100; /* High z-index to ensure always on top */
57
+ opacity: 0.5;
58
+ transition: opacity 0.2s;
59
+ border-radius: 4px;
60
+ padding: 2px;
61
+ }
62
+
63
+ /* Left button (<) - positioned on left side of divider */
64
+ .toggle-button-left {
65
+ left: -34px; /* 24px width + 10px spacing from divider */
66
+ top: 50%;
67
+ transform: translateY(-50%);
68
+ }
69
+
70
+ /* Right button (>) - positioned on right side of divider */
71
+ .toggle-button-right {
72
+ left: 2px;
73
+ top: 50%;
74
+ transform: translateY(-50%);
75
+ }
76
+
77
+ /* Up button (^) - positioned above divider */
78
+ .toggle-button-up {
79
+ top: -34px; /* 24px height + 10px spacing from divider */
80
+ left: 50%;
81
+ transform: translateX(-50%);
82
+ }
83
+
84
+ /* Down button (v) - positioned below divider */
85
+ .toggle-button-down {
86
+ top: 2px;
87
+ left: 50%;
88
+ transform: translateX(-50%);
89
+ }
90
+
91
+ /* Ensure buttons stay visible even when panels are collapsed */
92
+ .split > div:first-child {
93
+ min-width: 0 !important; /* Override any minimum width */
94
+ }
95
+
96
+ .split > div:nth-child(2) {
97
+ overflow: visible !important; /* Ensure buttons remain visible */
98
+ position: relative !important;
99
+ }
100
+
101
+ /* Collapsed state */
102
+ .collapsed-content {
103
+ display: none;
104
+ }
105
+
106
+ /* Gutter styles */
107
+ .gutter {
108
+ background-color: var(--panel-background-color);
109
+ background-repeat: no-repeat;
110
+ background-position: 50%;
111
+ transition: background-color 0.2s;
112
+ }
113
+
114
+ .gutter:hover {
115
+ background-color: var(--panel-surface-color);
116
+ }
117
+
118
+ .gutter.gutter-horizontal {
119
+ cursor: col-resize;
120
+ position: relative;
121
+ width: 10px;
122
+ z-index: 1;
123
+ }
124
+
125
+ .gutter.gutter-horizontal {
126
+ cursor: row-resize;
127
+ }
128
+
129
+ .gutter::after {
130
+ content: "";
131
+ position: absolute;
132
+ top: 50%;
133
+ left: 50%;
134
+ transform: translate(-50%, -50%);
135
+ -webkit-mask-image: url("handle.svg");
136
+ mask-image: url("handle.svg");
137
+ -webkit-mask-repeat: no-repeat;
138
+ mask-repeat: no-repeat;
139
+ background-color: var(--panel-border-color);
140
+ }
141
+
142
+ .gutter.gutter-horizontal::after {
143
+ width: 10px;
144
+ height: 24px;
145
+ }
146
+
147
+ .gutter.gutter-vertical::after {
148
+ width: 24px;
149
+ height: 10px;
150
+ -webkit-mask-image: url("handle_vertical.svg");
151
+ mask-image: url("handle_vertical.svg");
152
+ }
153
+
154
+ .split > div:nth-child(2) > div:not(.toggle-button-left):not(.toggle-button-right):not(.toggle-button-up):not(.toggle-button-down) {
155
+ width: 100%;
156
+ height: 100%;
157
+ overflow: auto;
158
+ padding-top: 36px; /* Space for the toggle buttons */
159
+ }
160
+
161
+ /* Toggle button base styles */
162
+ .toggle-button-left,
163
+ .toggle-button-right,
164
+ .toggle-button-up,
165
+ .toggle-button-down {
166
+ width: 24px;
167
+ height: 24px;
168
+ background-color: var(--panel-on-background-color);
169
+ -webkit-mask-repeat: no-repeat;
170
+ mask-repeat: no-repeat;
171
+ -webkit-mask-position: center;
172
+ mask-position: center;
173
+ -webkit-mask-size: 16px;
174
+ mask-size: 16px;
175
+ opacity: 0.5;
176
+ }
177
+
178
+ .toggle-button-left:hover, .toggle-button-right:hover, .toggle-button-up:hover, .toggle-button-down:hover {
179
+ opacity: 1;
180
+ }
181
+
182
+ /* Left arrow button */
183
+ .toggle-button-left {
184
+ -webkit-mask-image: url("arrow_left.svg");
185
+ mask-image: url("arrow_left.svg");
186
+ }
187
+
188
+ /* Right arrow button */
189
+ .toggle-button-right {
190
+ -webkit-mask-image: url("arrow_right.svg");
191
+ mask-image: url("arrow_right.svg");
192
+ }
193
+
194
+ /* Up arrow button */
195
+ .toggle-button-up {
196
+ -webkit-mask-image: url("arrow_up.svg");
197
+ mask-image: url("arrow_up.svg");
198
+ }
199
+
200
+ /* Down arrow button */
201
+ .toggle-button-down {
202
+ -webkit-mask-image: url("arrow_down.svg");
203
+ mask-image: url("arrow_down.svg");
204
+ }
205
+
206
+ /* Animation for toggle icon */
207
+ @keyframes jumpLeftRight {
208
+ 0%, 100% { transform: translateY(-50%); }
209
+ 25% { transform: translate(-4px, -50%); }
210
+ 50% { transform: translateY(-50%); }
211
+ 75% { transform: translate(4px, -50%); }
212
+ }
213
+
214
+ @keyframes jumpUpDown {
215
+ 0%, 100% { transform: translateX(-50%); }
216
+ 25% { transform: translate(-50%, -4px); }
217
+ 50% { transform: translateX(-50%); }
218
+ 75% { transform: translate(-50%, 4px); }
219
+ }
220
+
221
+ .toggle-button-left.animated, .toggle-button-right.animated {
222
+ animation-name: jumpLeftRight;
223
+ animation-duration: 0.5s;
224
+ animation-timing-function: ease;
225
+ animation-iteration-count: 3;
226
+ }
227
+
228
+ .toggle-button-up.animated, .toggle-button-down.animated {
229
+ animation-name: jumpUpDown;
230
+ animation-duration: 0.5s;
231
+ animation-timing-function: ease;
232
+ animation-iteration-count: 3;
233
+ }
234
+
235
+ /* List navigation styles */
236
+ ul.nav.flex-column {
237
+ padding-inline-start: 0;
238
+ margin: 0;
239
+ }
@@ -0,0 +1 @@
1
+ var he=Object.defineProperty;var Se=(a,i)=>{for(var s in i)he(a,s,{get:i[s],enumerable:!0})};var M={};Se(M,{render:()=>xe});var h=typeof window<"u"?window:null,J=h===null,G=J?void 0:h.document,S="addEventListener",z="removeEventListener",$="getBoundingClientRect",j="_a",y="_b",_="_c",q="horizontal",w=function(){return!1},ze=J?"calc":["","-webkit-","-moz-","-o-"].filter(function(a){var i=G.createElement("div");return i.style.cssText="width:"+a+"calc(9px)",!!i.style.length}).shift()+"calc",ie=function(a){return typeof a=="string"||a instanceof String},ae=function(a){if(ie(a)){var i=G.querySelector(a);if(!i)throw new Error("Selector "+a+" did not match a DOM element");return i}return a},p=function(a,i,s){var l=a[i];return l!==void 0?l:s},T=function(a,i,s,l){if(i){if(l==="end")return 0;if(l==="center")return a/2}else if(s){if(l==="start")return 0;if(l==="center")return a/2}return a},ye=function(a,i){var s=G.createElement("div");return s.className="gutter gutter-"+i,s},we=function(a,i,s){var l={};return ie(i)?l[a]=i:l[a]=ze+"("+i+"% - "+s+"px)",l},be=function(a,i){var s;return s={},s[a]=i+"px",s},Ee=function(a,i){if(i===void 0&&(i={}),J)return{};var s=a,l,b,E,B,C,o;Array.from&&(s=Array.from(s));var V=ae(s[0]),x=V.parentNode,L=getComputedStyle?getComputedStyle(x):null,O=L?L.flexDirection:null,D=p(i,"sizes")||s.map(function(){return 100/s.length}),F=p(i,"minSize",100),d=Array.isArray(F)?F:s.map(function(){return F}),u=p(i,"maxSize",1/0),v=Array.isArray(u)?u:s.map(function(){return u}),m=p(i,"expandToMin",!1),U=p(i,"gutterSize",10),H=p(i,"gutterAlign","center"),X=p(i,"snapOffset",30),le=Array.isArray(X)?X:s.map(function(){return X}),Y=p(i,"dragInterval",1),k=p(i,"direction",q),Z=p(i,"cursor",k===q?"col-resize":"row-resize"),oe=p(i,"gutter",ye),K=p(i,"elementStyle",we),ce=p(i,"gutterStyle",be);k===q?(l="width",b="clientX",E="left",B="right",C="clientWidth"):k==="vertical"&&(l="height",b="clientY",E="top",B="bottom",C="clientHeight");function P(n,e,t,r){var f=K(l,e,t,r);Object.keys(f).forEach(function(c){n.style[c]=f[c]})}function ue(n,e,t){var r=ce(l,e,t);Object.keys(r).forEach(function(f){n.style[f]=r[f]})}function I(){return o.map(function(n){return n.size})}function Q(n){return"touches"in n?n.touches[0][b]:n[b]}function ee(n){var e=o[this.a],t=o[this.b],r=e.size+t.size;e.size=n/this.size*r,t.size=r-n/this.size*r,P(e.element,e.size,this[y],e.i),P(t.element,t.size,this[_],t.i)}function fe(n){var e,t=o[this.a],r=o[this.b];this.dragging&&(e=Q(n)-this.start+(this[y]-this.dragOffset),Y>1&&(e=Math.round(e/Y)*Y),e<=t.minSize+t.snapOffset+this[y]?e=t.minSize+this[y]:e>=this.size-(r.minSize+r.snapOffset+this[_])&&(e=this.size-(r.minSize+this[_])),e>=t.maxSize-t.snapOffset+this[y]?e=t.maxSize+this[y]:e<=this.size-(r.maxSize-r.snapOffset+this[_])&&(e=this.size-(r.maxSize+this[_])),ee.call(this,e),p(i,"onDrag",w)(I()))}function te(){var n=o[this.a].element,e=o[this.b].element,t=n[$](),r=e[$]();this.size=t[l]+r[l]+this[y]+this[_],this.start=t[E],this.end=t[B]}function de(n){if(!getComputedStyle)return null;var e=getComputedStyle(n);if(!e)return null;var t=n[C];return t===0?null:(k===q?t-=parseFloat(e.paddingLeft)+parseFloat(e.paddingRight):t-=parseFloat(e.paddingTop)+parseFloat(e.paddingBottom),t)}function ne(n){var e=de(x);if(e===null||d.reduce(function(c,g){return c+g},0)>e)return n;var t=0,r=[],f=n.map(function(c,g){var N=e*c/100,R=T(U,g===0,g===n.length-1,H),W=d[g]+R;return N<W?(t+=W-N,r.push(0),W):(r.push(N-W),N)});return t===0?n:f.map(function(c,g){var N=c;if(t>0&&r[g]-t>0){var R=Math.min(t,r[g]-t);t-=R,N=c-R}return N/e*100})}function ve(){var n=this,e=o[n.a].element,t=o[n.b].element;n.dragging&&p(i,"onDragEnd",w)(I()),n.dragging=!1,h[z]("mouseup",n.stop),h[z]("touchend",n.stop),h[z]("touchcancel",n.stop),h[z]("mousemove",n.move),h[z]("touchmove",n.move),n.stop=null,n.move=null,e[z]("selectstart",w),e[z]("dragstart",w),t[z]("selectstart",w),t[z]("dragstart",w),e.style.userSelect="",e.style.webkitUserSelect="",e.style.MozUserSelect="",e.style.pointerEvents="",t.style.userSelect="",t.style.webkitUserSelect="",t.style.MozUserSelect="",t.style.pointerEvents="",n.gutter.style.cursor="",n.parent.style.cursor="",G.body.style.cursor=""}function pe(n){if(!("button"in n&&n.button!==0)){var e=this,t=o[e.a].element,r=o[e.b].element;e.dragging||p(i,"onDragStart",w)(I()),n.preventDefault(),e.dragging=!0,e.move=fe.bind(e),e.stop=ve.bind(e),h[S]("mouseup",e.stop),h[S]("touchend",e.stop),h[S]("touchcancel",e.stop),h[S]("mousemove",e.move),h[S]("touchmove",e.move),t[S]("selectstart",w),t[S]("dragstart",w),r[S]("selectstart",w),r[S]("dragstart",w),t.style.userSelect="none",t.style.webkitUserSelect="none",t.style.MozUserSelect="none",t.style.pointerEvents="none",r.style.userSelect="none",r.style.webkitUserSelect="none",r.style.MozUserSelect="none",r.style.pointerEvents="none",e.gutter.style.cursor=Z,e.parent.style.cursor=Z,G.body.style.cursor=Z,te.call(e),e.dragOffset=Q(n)-e.end}}D=ne(D);var A=[];o=s.map(function(n,e){var t={element:ae(n),size:D[e],minSize:d[e],maxSize:v[e],snapOffset:le[e],i:e},r;if(e>0&&(r={a:e-1,b:e,dragging:!1,direction:k,parent:x},r[y]=T(U,e-1===0,!1,H),r[_]=T(U,!1,e===s.length-1,H),O==="row-reverse"||O==="column-reverse")){var f=r.a;r.a=r.b,r.b=f}if(e>0){var c=oe(e,k,t.element);ue(c,U,e),r[j]=pe.bind(r),c[S]("mousedown",r[j]),c[S]("touchstart",r[j]),x.insertBefore(c,t.element),r.gutter=c}return P(t.element,t.size,T(U,e===0,e===s.length-1,H),e),e>0&&A.push(r),t});function re(n){var e=n.i===A.length,t=e?A[n.i-1]:A[n.i];te.call(t);var r=e?t.size-n.minSize-t[_]:n.minSize+t[y];ee.call(t,r)}o.forEach(function(n){var e=n.element[$]()[l];e<n.minSize&&(m?re(n):n.minSize=e)});function me(n){var e=ne(n);e.forEach(function(t,r){if(r>0){var f=A[r-1],c=o[f.a],g=o[f.b];c.size=e[r-1],g.size=t,P(c.element,c.size,f[y],c.i),P(g.element,g.size,f[_],g.i)}})}function ge(n,e){A.forEach(function(t){if(e!==!0?t.parent.removeChild(t.gutter):(t.gutter[z]("mousedown",t[j]),t.gutter[z]("touchstart",t[j])),n!==!0){var r=K(l,t.a.size,t[y]);Object.keys(r).forEach(function(f){o[t.a].element.style[f]="",o[t.b].element.style[f]=""})}})}return{setSizes:me,getSizes:I,collapse:function(e){re(o[e])},destroy:ge,parent:x,pairs:A}},se=Ee;function xe({model:a,view:i}){let s=document.createElement("div");s.className=`split ${a.orientation}`,s.classList.add("loading");let l=document.createElement("div"),b=document.createElement("div");s.append(l,b);let E=document.createElement("div");if(E.classList.add("content-wrapper"),a.show_buttons){let d=document.createElement("div"),u=document.createElement("div");a.orientation==="horizontal"?(d.className="toggle-button-left",u.className="toggle-button-right"):(d.className="toggle-button-up",u.className="toggle-button-down"),b.appendChild(d),b.appendChild(u),d.addEventListener("click",()=>{C++,o=0;let v;C===1?v=[50,50]:(v=[0,100],C=0),x.setSizes(v);let m=v[1]<=5;a.send_msg({collapsed:m}),a.collapsed=m,L(m,v),i.invalidate_layout()}),u.addEventListener("click",()=>{o++,C=0;let v;o===1?v=[50,50]:(v=[100,0],o=0),x.setSizes(v);let m=v[1]<=5;a.send_msg({collapsed:m}),a.collapsed=m,L(m,v),i.invalidate_layout()})}let B=a.collapsed?[100,0]:a.expanded_sizes,C=0,o=0;function V(){C=0,o=0}let x=se([l,b],{sizes:B,minSize:[0,0],gutterSize:8,direction:a.orientation,onDragEnd:d=>{i.invalidate_layout();let u=d[1]<=5,v=d[0]<=5,m=u;a.collapsed!==m&&(a.send_msg({collapsed:m}),a.collapsed=m),L(m,d),V()}});function L(d,u=null){let v=u?u[0]<=5:!1;(u?u[1]<=5:!1)?E.className="collapsed-content":E.className="content-wrapper",v?O.className="collapsed-content":O.className="content-wrapper"}a.on("msg:custom",d=>{if(d.type==="update_collapsed"){let u=d.collapsed;a.collapsed=u,u?x.setSizes([100,0]):x.setSizes(a.expanded_sizes),L(u),i.invalidate_layout()}}),a.on("after_layout",()=>{setTimeout(()=>{s.classList.remove("loading"),a.show_buttons&&!window._toggleAnimationShown&&(leftArrowButton.classList.add("animated"),rightArrowButton.classList.add("animated"),setTimeout(()=>{leftArrowButton.classList.remove("animated"),rightArrowButton.classList.remove("animated"),window._toggleAnimationShown=!0},1500)),window.dispatchEvent(new Event("resize"))},100)});let O=document.createElement("div");O.classList.add("left-content-wrapper"),a.collapsed&&(E.className="collapsed-content"),O.classList.add("left-panel-content");let[D,F]=a.get_child("objects");return O.append(D),l.append(O),E.append(F),b.append(E),s}var Le={HSplit:M,Split:M,VSplit:M};export{Le as default};
@@ -0,0 +1,215 @@
1
+ import Split from "https://esm.sh/split.js@1.6.5"
2
+
3
+ export function render({ model, view }) {
4
+ const splitDiv = document.createElement("div")
5
+ splitDiv.className = `split ${model.orientation}`
6
+ splitDiv.classList.add("loading")
7
+
8
+ const split0 = document.createElement("div")
9
+ const split1 = document.createElement("div")
10
+ splitDiv.append(split0, split1)
11
+
12
+ // Create content wrapper for right panel
13
+ const contentWrapper = document.createElement("div")
14
+ contentWrapper.classList.add("content-wrapper")
15
+
16
+ if (model.show_buttons) {
17
+ // Create toggle icons for both sides of the divider
18
+ const leftArrowButton = document.createElement("div") // < button
19
+ const rightArrowButton = document.createElement("div") // > button
20
+
21
+ // Set button icons based on orientation
22
+ if (model.orientation === "horizontal") {
23
+ leftArrowButton.className = "toggle-button-left"
24
+ rightArrowButton.className = "toggle-button-right"
25
+ } else {
26
+ leftArrowButton.className = "toggle-button-up"
27
+ rightArrowButton.className = "toggle-button-down"
28
+ }
29
+
30
+ // Add both buttons to the right panel (split1) so they're positioned relative to the divider
31
+ split1.appendChild(leftArrowButton)
32
+ split1.appendChild(rightArrowButton)
33
+
34
+ // Left/Up arrow button event listener - two-step toggle
35
+ leftArrowButton.addEventListener("click", () => {
36
+ leftClickCount++
37
+ rightClickCount = 0 // Reset other button's count
38
+
39
+ let newSizes
40
+
41
+ if (leftClickCount === 1) {
42
+ // First tap: make sizes 50, 50
43
+ newSizes = [50, 50]
44
+ } else {
45
+ // Second tap (or more): make sizes 0, 100
46
+ newSizes = [0, 100]
47
+ leftClickCount = 0 // Reset after second tap
48
+ }
49
+
50
+ splitInstance.setSizes(newSizes)
51
+
52
+ // Update collapsed state based on new sizes
53
+ const newCollapsedState = newSizes[1] <= 5
54
+ model.send_msg({ collapsed: newCollapsedState })
55
+ model.collapsed = newCollapsedState
56
+
57
+ updateUIForCollapsedState(newCollapsedState, newSizes)
58
+ view.invalidate_layout()
59
+ })
60
+
61
+ // Right/Down arrow button event listener - two-step toggle
62
+ rightArrowButton.addEventListener("click", () => {
63
+ rightClickCount++
64
+ leftClickCount = 0 // Reset other button's count
65
+
66
+ let newSizes
67
+
68
+ if (rightClickCount === 1) {
69
+ // First tap: make sizes 50, 50
70
+ newSizes = [50, 50]
71
+ } else {
72
+ // Second tap (or more): make sizes 100, 0
73
+ newSizes = [100, 0]
74
+ rightClickCount = 0 // Reset after second tap
75
+ }
76
+
77
+ splitInstance.setSizes(newSizes)
78
+
79
+ // Update collapsed state based on new sizes
80
+ const newCollapsedState = newSizes[1] <= 5
81
+ model.send_msg({ collapsed: newCollapsedState })
82
+ model.collapsed = newCollapsedState
83
+
84
+ updateUIForCollapsedState(newCollapsedState, newSizes)
85
+ view.invalidate_layout()
86
+ })
87
+ }
88
+
89
+ // Determine initial state - collapsed means right panel is hidden
90
+ const initSizes = model.collapsed ? [100, 0] : model.expanded_sizes
91
+
92
+ // Track click counts for toggle behavior
93
+ let leftClickCount = 0 // For < button
94
+ let rightClickCount = 0 // For > button
95
+
96
+ // Function to reset click counts when sizes change via dragging
97
+ function resetClickCounts() {
98
+ leftClickCount = 0
99
+ rightClickCount = 0
100
+ }
101
+
102
+ // Use minSize of 0 to allow full collapse via buttons
103
+ const splitInstance = Split([split0, split1], {
104
+ sizes: initSizes,
105
+ minSize: [0, 0], // Allow full collapse for both panels
106
+ gutterSize: 8, // Match the 8px width in CSS
107
+ direction: model.orientation,
108
+ onDragEnd: (sizes) => {
109
+ view.invalidate_layout()
110
+
111
+ // Determine the new collapsed state based on panel sizes
112
+ const rightPanelCollapsed = sizes[1] <= 5
113
+ const leftPanelCollapsed = sizes[0] <= 5
114
+
115
+ // The model's collapsed state represents whether the right panel is collapsed
116
+ const newCollapsedState = rightPanelCollapsed
117
+
118
+ if (model.collapsed !== newCollapsedState) {
119
+ // Send message to Python about collapsed state change
120
+ model.send_msg({ collapsed: newCollapsedState })
121
+ model.collapsed = newCollapsedState
122
+ }
123
+
124
+ // Update UI based on current sizes
125
+ updateUIForCollapsedState(newCollapsedState, sizes)
126
+
127
+ // Reset click counts when user drags the splitter
128
+ resetClickCounts()
129
+ },
130
+ })
131
+
132
+ // Function to update UI elements based on collapsed state
133
+ function updateUIForCollapsedState(isCollapsed, sizes = null) {
134
+ // Determine current panel state
135
+ const leftPanelHidden = sizes ? sizes[0] <= 5 : false
136
+ const rightPanelHidden = sizes ? sizes[1] <= 5 : false
137
+
138
+ // Update content visibility
139
+ if (rightPanelHidden) {
140
+ contentWrapper.className = "collapsed-content"
141
+ } else {
142
+ contentWrapper.className = "content-wrapper"
143
+ }
144
+
145
+ if (leftPanelHidden) {
146
+ leftContentWrapper.className = "collapsed-content"
147
+ } else {
148
+ leftContentWrapper.className = "content-wrapper"
149
+ }
150
+ }
151
+
152
+ // Listen for collapsed state changes from Python
153
+ model.on("msg:custom", (event) => {
154
+ if (event.type === "update_collapsed") {
155
+ const newCollapsedState = event.collapsed
156
+
157
+ model.collapsed = newCollapsedState
158
+
159
+ // Update split sizes based on new collapsed state
160
+ if (newCollapsedState) {
161
+ // Collapse right panel (show only left)
162
+ splitInstance.setSizes([100, 0])
163
+ } else {
164
+ // Expand to show both panels
165
+ splitInstance.setSizes(model.expanded_sizes)
166
+ }
167
+
168
+ updateUIForCollapsedState(newCollapsedState)
169
+ view.invalidate_layout()
170
+ }
171
+ })
172
+
173
+ model.on("after_layout", () => {
174
+ setTimeout(() => {
175
+ splitDiv.classList.remove("loading")
176
+
177
+ // Only add animation on initial load
178
+ if (model.show_buttons && !window._toggleAnimationShown) {
179
+ // Add animation on first load only
180
+ leftArrowButton.classList.add("animated")
181
+ rightArrowButton.classList.add("animated")
182
+
183
+ // Remove animation after it completes and set flag
184
+ setTimeout(() => {
185
+ leftArrowButton.classList.remove("animated")
186
+ rightArrowButton.classList.remove("animated")
187
+ window._toggleAnimationShown = true
188
+ }, 1500)
189
+ }
190
+
191
+ window.dispatchEvent(new Event("resize"))
192
+ }, 100)
193
+ })
194
+
195
+ // Create a centered content wrapper for the left panel
196
+ const leftContentWrapper = document.createElement("div")
197
+ leftContentWrapper.classList.add("left-content-wrapper")
198
+
199
+ // Set initial display based on collapsed state
200
+ if (model.collapsed) {
201
+ contentWrapper.className = "collapsed-content"
202
+ }
203
+
204
+ // Apply left-panel-content class to the left panel
205
+ leftContentWrapper.classList.add("left-panel-content")
206
+
207
+ // Append children to the appropriate containers
208
+ const [left, right] = model.get_child("objects")
209
+ leftContentWrapper.append(left)
210
+ split0.append(leftContentWrapper)
211
+ contentWrapper.append(right)
212
+ split1.append(contentWrapper)
213
+
214
+ return splitDiv
215
+ }
@@ -0,0 +1,325 @@
1
+ Metadata-Version: 2.4
2
+ Name: panel-splitjs
3
+ Version: 0.0.1a0
4
+ Summary: Provides split.js components for Panel.
5
+ Project-URL: Homepage, https://github.com/panel-extensions/panel-splitjs
6
+ Project-URL: Source, https://github.com/panel-extensions/panel-splitjs
7
+ Author-email: Philipp Rudiger <philipp.jfr@gmail.com>
8
+ Maintainer-email: Philipp Rudiger <philipp.jfr@gmail.com>
9
+ License: BSD
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: packaging
25
+ Requires-Dist: panel>=1.8.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: mkdocs-material; extra == 'dev'
28
+ Requires-Dist: mkdocstrings[python]; extra == 'dev'
29
+ Requires-Dist: pytest; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio; extra == 'dev'
31
+ Requires-Dist: pytest-rerunfailures; extra == 'dev'
32
+ Requires-Dist: pytest-xdist; extra == 'dev'
33
+ Requires-Dist: watchfiles; extra == 'dev'
34
+ Provides-Extra: mypy
35
+ Requires-Dist: mypy; extra == 'mypy'
36
+ Requires-Dist: types-requests; extra == 'mypy'
37
+ Requires-Dist: typing-extensions; extra == 'mypy'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # panel-splitjs
41
+
42
+ [![CI](https://img.shields.io/github/actions/workflow/status/panel-extensions/panel-splitjs/ci.yml?style=flat-square&branch=main)](https://github.com/panel-extensions/panel-splitjs/actions/workflows/ci.yml)
43
+ [![conda-forge](https://img.shields.io/conda/vn/conda-forge/panel-splitjs?logoColor=white&logo=conda-forge&style=flat-square)](https://prefix.dev/channels/conda-forge/packages/panel-splitjs)
44
+ [![pypi-version](https://img.shields.io/pypi/v/panel-splitjs.svg?logo=pypi&logoColor=white&style=flat-square)](https://pypi.org/project/panel-splitjs)
45
+ [![python-version](https://img.shields.io/pypi/pyversions/panel-splitjs?logoColor=white&logo=python&style=flat-square)](https://pypi.org/project/panel-splitjs)
46
+
47
+ A responsive, draggable split panel component for [Panel](https://panel.holoviz.org) applications, powered by [split.js](https://split.js.org/).
48
+
49
+ ## Features
50
+
51
+ - **Draggable divider** - Resize panels by dragging the divider between them
52
+ - **Collapsible panels** - Toggle panels open/closed with optional buttons
53
+ - **Flexible orientation** - Support for both horizontal and vertical splits
54
+ - **Minimum size constraints** - Enforce minimum panel sizes to prevent over-collapse
55
+ - **Smooth animations** - Beautiful transitions when toggling panels
56
+ - **Customizable sizes** - Control initial and expanded panel sizes
57
+ - **Invertible layout** - Swap panel positions and button locations
58
+
59
+ ## Installation
60
+
61
+ Install via pip:
62
+
63
+ ```bash
64
+ pip install panel-splitjs
65
+ ```
66
+
67
+ Or via conda:
68
+
69
+ ```bash
70
+ conda install -c conda-forge panel-splitjs
71
+ ```
72
+
73
+ ## Quick Start
74
+
75
+ ```python
76
+ import panel as pn
77
+ from panel_splitjs import Split
78
+
79
+ pn.extension()
80
+
81
+ # Create a simple split layout
82
+ split = Split(
83
+ pn.pane.Markdown("## Left Panel\nContent here"),
84
+ pn.pane.Markdown("## Right Panel\nMore content"),
85
+ sizes=(50, 50), # Equal sizing
86
+ show_buttons=True
87
+ )
88
+
89
+ split.servable()
90
+ ```
91
+
92
+ ## Usage Examples
93
+
94
+ ### Basic Horizontal Split
95
+
96
+ ```python
97
+ import panel as pn
98
+ from panel_splitjs import HSplit
99
+
100
+ pn.extension()
101
+
102
+ left_panel = pn.Column(
103
+ "# Main Content",
104
+ pn.widgets.TextInput(name="Input"),
105
+ pn.pane.Markdown("This is the main content area.")
106
+ )
107
+
108
+ right_panel = pn.Column(
109
+ "# Sidebar",
110
+ pn.widgets.Select(name="Options", options=["A", "B", "C"]),
111
+ )
112
+
113
+ split = HSplit(
114
+ left_panel,
115
+ right_panel,
116
+ sizes=(70, 30), # 70% left, 30% right
117
+ show_buttons=True
118
+ )
119
+
120
+ split.servable()
121
+ ```
122
+
123
+ ### Vertical Split
124
+
125
+ ```python
126
+ import panel as pn
127
+ from panel_splitjs import VSplit
128
+
129
+ pn.extension()
130
+
131
+ top_panel = pn.pane.Markdown("## Top Section\nHeader content")
132
+ bottom_panel = pn.pane.Markdown("## Bottom Section\nFooter content")
133
+
134
+ split = VSplit(
135
+ top_panel,
136
+ bottom_panel,
137
+ sizes=(60, 40),
138
+ orientation="vertical"
139
+ )
140
+
141
+ split.servable()
142
+ ```
143
+
144
+ ### Collapsible Sidebar
145
+
146
+ ```python
147
+ import panel as pn
148
+ from panel_splitjs import Split
149
+
150
+ pn.extension()
151
+
152
+ # Start with sidebar collapsed
153
+ split = Split(
154
+ pn.pane.Markdown("## Main Content"),
155
+ pn.pane.Markdown("## Collapsible Sidebar"),
156
+ collapsed=True,
157
+ expanded_sizes=(65, 35), # When expanded, 65% main, 35% sidebar
158
+ show_buttons=True,
159
+ min_sizes=(200, 200) # Minimum 200px for each panel
160
+ )
161
+
162
+ # Toggle collapse programmatically
163
+ button = pn.widgets.Button(name="Toggle Sidebar")
164
+ button.on_click(lambda e: setattr(split, 'collapsed', not split.collapsed))
165
+
166
+ pn.Column(button, split).servable()
167
+ ```
168
+
169
+ ### Inverted Layout
170
+
171
+ ```python
172
+ import panel as pn
173
+ from panel_splitjs import Split
174
+
175
+ pn.extension()
176
+
177
+ # Inverted: right panel collapses, button on right side
178
+ split = Split(
179
+ pn.pane.Markdown("## Secondary Panel"),
180
+ pn.pane.Markdown("## Main Content"),
181
+ invert=True, # Swap layout and button position
182
+ collapsed=True,
183
+ expanded_sizes=(35, 65),
184
+ show_buttons=True
185
+ )
186
+
187
+ split.servable()
188
+ ```
189
+
190
+ ## API Reference
191
+
192
+ ### Split
193
+
194
+ The main split panel component with full customization options.
195
+
196
+ **Parameters:**
197
+
198
+ - `objects` (list): Two Panel components to display in the split panels
199
+ - `collapsed` (bool, default=False): Whether the secondary panel is collapsed
200
+ - `expanded_sizes` (tuple, default=(50, 50)): Percentage sizes when expanded (must sum to 100)
201
+ - `invert` (bool, default=False): Swap panel positions and button locations (constant after init)
202
+ - `min_sizes` (tuple, default=(0, 0)): Minimum sizes in pixels for each panel
203
+ - `orientation` (str, default="horizontal"): Either "horizontal" or "vertical"
204
+ - `show_buttons` (bool, default=False): Show collapse/expand toggle buttons
205
+ - `sizes` (tuple, default=(100, 0)): Initial percentage sizes (must sum to 100)
206
+
207
+ ### HSplit
208
+
209
+ Horizontal split panel (convenience class).
210
+
211
+ Same parameters as `Split` but `orientation` is locked to "horizontal".
212
+
213
+ ### VSplit
214
+
215
+ Vertical split panel (convenience class).
216
+
217
+ Same parameters as `Split` but `orientation` is locked to "vertical".
218
+
219
+ ## Common Use Cases
220
+
221
+ ### Chat Interface with Output
222
+
223
+ ```python
224
+ import panel as pn
225
+ from panel_splitjs import Split
226
+
227
+ pn.extension()
228
+
229
+ chat = pn.chat.ChatInterface()
230
+ output = pn.Column("# Output Area")
231
+
232
+ split = Split(
233
+ chat,
234
+ output,
235
+ collapsed=False,
236
+ expanded_sizes=(50, 50),
237
+ show_buttons=True,
238
+ min_sizes=(300, 300)
239
+ )
240
+
241
+ split.servable()
242
+ ```
243
+
244
+ ### Dashboard with Collapsible Controls
245
+
246
+ ```python
247
+ import panel as pn
248
+ from panel_splitjs import Split
249
+
250
+ pn.extension()
251
+
252
+ controls = pn.Column(
253
+ pn.widgets.Select(name="Dataset", options=["A", "B", "C"]),
254
+ pn.widgets.IntSlider(name="Threshold", start=0, end=100),
255
+ pn.widgets.Button(name="Update")
256
+ )
257
+
258
+ visualization = pn.pane.Markdown("## Main Visualization Area")
259
+
260
+ split = Split(
261
+ controls,
262
+ visualization,
263
+ collapsed=True,
264
+ expanded_sizes=(25, 75),
265
+ show_buttons=True,
266
+ min_sizes=(250, 400)
267
+ )
268
+
269
+ split.servable()
270
+ ```
271
+
272
+ ### Responsive Layout
273
+
274
+ ```python
275
+ import panel as pn
276
+ from panel_splitjs import Split
277
+
278
+ pn.extension()
279
+
280
+ # Automatically adjust to available space
281
+ split = Split(
282
+ pn.pane.Markdown("## Panel 1\nResponsive content"),
283
+ pn.pane.Markdown("## Panel 2\nMore responsive content"),
284
+ sizes=(50, 50),
285
+ min_sizes=(200, 200), # Prevent panels from getting too small
286
+ show_buttons=True
287
+ )
288
+
289
+ split.servable()
290
+ ```
291
+
292
+ ## Development
293
+
294
+ This project is managed by [pixi](https://pixi.sh).
295
+
296
+ ### Setup
297
+
298
+ ```bash
299
+ git clone https://github.com/panel-extensions/panel-splitjs
300
+ cd panel-splitjs
301
+
302
+ pixi run pre-commit-install
303
+ pixi run postinstall
304
+ pixi run test
305
+ ```
306
+
307
+ ### Building
308
+
309
+ ```bash
310
+ pixi run build
311
+ ```
312
+
313
+ ### Testing
314
+
315
+ ```bash
316
+ pixi run test
317
+ ```
318
+
319
+ ## Contributing
320
+
321
+ Contributions are welcome! Please feel free to submit a Pull Request.
322
+
323
+ ## License
324
+
325
+ See LICENSE file for details.
@@ -0,0 +1,16 @@
1
+ panel_splitjs/__init__.py,sha256=3o1ZsgXufccivyXhnMunBBNQZliCwQY4GW9XKQ9gRDM,124
2
+ panel_splitjs/__version.py,sha256=rpIYpR7RLjD5NBNasrT3f60C0qTL-nnHBifOofA_qyo,1778
3
+ panel_splitjs/_version.py,sha256=N6jqqryygxntTpQZELt2H0LAGZ4wKgVPTWGAhPzx98U,712
4
+ panel_splitjs/base.py,sha256=OcdprAQyY--hTgiMCUVOFw2RsuknlS46o3DUv89lO9E,5337
5
+ panel_splitjs/dist/panel-splitjs.bundle.js,sha256=tcWSuP8-WKzPVq1qsV_FnnFBq_eGB32AqnKu3cwIXpc,8466
6
+ panel_splitjs/dist/css/arrow_down.svg,sha256=pPSAQIH-UkJf1HbWJhAapaT-rROqn6JVPQldOT3Al0I,214
7
+ panel_splitjs/dist/css/arrow_left.svg,sha256=XZ1Qf7CzKTij-Fa4Up51Gbu_WnBcScK7Qx-tOprvEzE,215
8
+ panel_splitjs/dist/css/arrow_right.svg,sha256=_-3m5dLhPwH9caIEk8XLp3Z8-xQHHvTBny6AcyFVRb4,214
9
+ panel_splitjs/dist/css/arrow_up.svg,sha256=FoVQYz0kh1SAf7EJAs2ZI-ZuuQjcPzR3uM4uViZ87Qs,215
10
+ panel_splitjs/dist/css/handle.svg,sha256=tEQAE3lNBVzPigcp9Z0SQZCW0bSzfECYIvpl19NOd0E,899
11
+ panel_splitjs/dist/css/handle_vertical.svg,sha256=2QZdZzNiLaJp93Ot4tJBhfGjF07EiMfN-Hq3Uf5Z_BI,801
12
+ panel_splitjs/dist/css/splitjs.css,sha256=CIo6tHTT0_NpYjoJFxtCB60uKBUBEFYtvyphDsQpviM,5257
13
+ panel_splitjs/models/splitjs.js,sha256=kDkD-CTnPhRmsE1y6MCIMr31y90LS2r2J3EkFbDUZu0,6965
14
+ panel_splitjs-0.0.1a0.dist-info/METADATA,sha256=9TkauebpnY0rouEEtKJq0sb1-Bt9VtJfLrYGPaEBkr0,8178
15
+ panel_splitjs-0.0.1a0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ panel_splitjs-0.0.1a0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any