Skip to content

Commit a92a26e

Browse files
committed
maint: make scrollable widget native
1 parent 185e9ed commit a92a26e

File tree

3 files changed

+53
-36
lines changed

3 files changed

+53
-36
lines changed

pywebio/html/css/app.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,16 @@ details[open]>summary {
289289

290290
.pywebio-clickable{
291291
cursor: pointer;
292+
}
293+
294+
/* scrollable widget */
295+
.webio-scrollable{
296+
overflow-y: scroll;
297+
padding: 10px;
298+
margin-bottom: 10px;
299+
}
300+
301+
.webio-scrollable.scrollable-border{
302+
border: 1px solid rgba(0,0,0,.125);
303+
box-shadow: inset 0 0 2px 0 rgba(0,0,0,.1);
292304
}

pywebio/output.py

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,7 @@ def put_scrollable(content=[], height=400, keep_bottom=False, horizon_scroll=Fal
10671067
:param int/tuple height: The height of the area (in pixels).
10681068
``height`` parameter also accepts ``(min_height, max_height)`` to indicate the range of height, for example,
10691069
``(100, 200)`` means that the area has a minimum height of 100 pixels and a maximum of 200 pixels.
1070+
:param bool keep_bottom: Whether to keep the content area scrolled to the bottom when updated.
10701071
:param bool horizon_scroll: Whether to use the horizontal scroll bar
10711072
:param bool border: Whether to show border
10721073
:param int scope, position: Those arguments have the same meaning as for `put_text()`
@@ -1106,41 +1107,10 @@ def put_scrollable(content=[], height=400, keep_bottom=False, horizon_scroll=Fal
11061107
except Exception:
11071108
min_height, max_height = height, height
11081109

1109-
dom_id = 'pywebio-%s' % random_str(10)
1110-
1111-
tpl = """<div id="{{dom_id}}" {{#keep_bottom}}tabindex="0"{{/keep_bottom}}
1112-
style="min-height: {{min_height}}px; max-height: {{max_height}}px;
1113-
overflow-y: scroll;
1114-
{{#horizon_scroll}}overflow-x: scroll;{{/horizon_scroll}}
1115-
{{#border}}
1116-
border: 1px solid rgba(0,0,0,.125);
1117-
box-shadow: inset 0 0 2px 0 rgba(0,0,0,.1);
1118-
{{/border}}
1119-
padding: 10px;
1120-
margin-bottom: 10px;">
1121-
1122-
{{#contents}}
1123-
{{& pywebio_output_parse}}
1124-
{{/contents}}
1125-
</div>"""
1126-
if keep_bottom:
1127-
tpl += """
1128-
<script>
1129-
(function(){
1130-
let div = document.getElementById(%r), stop=false;
1131-
$(div).on('focusin', function(e){ stop=true }).on('focusout', function(e){ stop=false });;
1132-
new MutationObserver(function (mutations, observe) {
1133-
if(!stop) $(div).stop().animate({ scrollTop: $(div).prop("scrollHeight")}, 200);
1134-
}).observe(div, { childList: true, subtree:true });
1135-
})();
1136-
</script>
1137-
""" % dom_id
1138-
1139-
return put_widget(template=tpl,
1140-
data=dict(dom_id=dom_id, contents=content, min_height=min_height,
1141-
max_height=max_height, keep_bottom=keep_bottom,
1142-
horizon_scroll=horizon_scroll, border=border),
1143-
scope=scope, position=position).enable_context_manager()
1110+
spec = _get_output_spec('scrollable', contents=content, min_height=min_height, max_height=max_height,
1111+
keep_bottom=keep_bottom, horizon_scroll=horizon_scroll, border=border, scope=scope,
1112+
position=position)
1113+
return Output(spec)
11441114

11451115

11461116
@safely_destruct_output_when_exp('tabs')

webiojs/src/models/output.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,40 @@ let TabsWidget = {
201201
}
202202
};
203203

204+
const SCROLLABLE_TPL = `<div>
205+
<div class="webio-scrollable{{#border}} scrollable-border{{/border}}" {{#keep_bottom}}tabindex="0"{{/keep_bottom}}
206+
style="min-height: {{min_height}}px; max-height: {{max_height}}px;{{#horizon_scroll}}overflow-x: scroll;{{/horizon_scroll}}">
207+
{{#contents}}
208+
{{& pywebio_output_parse}}
209+
{{/contents}}
210+
</div>
211+
</div>`;
212+
213+
let ScrollableWidget = {
214+
handle_type: 'scrollable',
215+
get_element: function (spec: {
216+
contents: any, min_height: string,
217+
max_height: string, keep_bottom: boolean,
218+
horizon_scroll: boolean, border: boolean
219+
}) {
220+
let elem = render_tpl(SCROLLABLE_TPL, spec);
221+
let container = elem.find('> div');
222+
if (spec.keep_bottom) {
223+
let stop = false;
224+
container.on('focusin mouseenter', function (e) {
225+
stop = true
226+
}).on('focusout mouseleave', function (e) {
227+
stop = false
228+
});
229+
console.log(container)
230+
new MutationObserver(function (mutations, observe) {
231+
if (!stop) container.stop().animate({scrollTop: container.prop("scrollHeight")}, 200);
232+
}).observe(container[0], {childList: true, subtree: true});
233+
}
234+
return elem;
235+
}
236+
};
237+
204238

205239
const SCOPE_TPL = `<div>
206240
{{#contents}}
@@ -233,7 +267,8 @@ let CustomWidget = {
233267
}
234268
};
235269

236-
let all_widgets: Widget[] = [Text, Markdown, Html, Buttons, File, Table, CustomWidget, TabsWidget, PinWidget, ScopeWidget];
270+
let all_widgets: Widget[] = [Text, Markdown, Html, Buttons, File, Table, CustomWidget, TabsWidget, PinWidget,
271+
ScopeWidget, ScrollableWidget];
237272

238273

239274
let type2widget: { [i: string]: Widget } = {};

0 commit comments

Comments
 (0)