Skip to content

Commit ec310b0

Browse files
committed
Add disable_scroll option; Refactor updateStyles -> updateConfig and add disable_scroll; Improve debug messages;
1 parent f667df5 commit ec310b0

File tree

3 files changed

+89
-45
lines changed

3 files changed

+89
-45
lines changed

streamlit_scroll_navigation/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def scroll_navbar(
6969
orientation: Literal['vertical', 'horizontal'] = 'vertical',
7070
override_styles: Dict[str, str] = {},
7171
auto_update_anchor: bool = True,
72+
disable_scroll: bool = False,
7273
) -> str:
7374
"""
7475
Creates a scroll navigation bar component.
@@ -93,8 +94,11 @@ def scroll_navbar(
9394
override_styles (Dict[str, str], optional):
9495
A dictionary of styles to override default styles. Defaults to {}.
9596
auto_update_anchor (bool, optional):
96-
If true, the highlighted anchor will automatically update to the next nearest anchor when the current one is scrolled out of view.
97+
If True, the highlighted anchor will automatically update to the next nearest anchor when the current one is scrolled out of view.
9798
Defaults to True.
99+
disable_scroll (bool, optional):
100+
If True, disables the scroll functionality of the navigation bar.
101+
Defaults to False.
98102
Returns:
99103
str: The ID of the anchor that is currently selected.
100104
Example:
@@ -122,5 +126,6 @@ def scroll_navbar(
122126
orientation=orientation,
123127
override_styles=override_styles,
124128
auto_update_anchor=auto_update_anchor,
129+
disable_scroll=disable_scroll,
125130
)
126131
return component_value

streamlit_scroll_navigation/frontend/public/CrossOriginInterface.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,31 @@ class CrossOriginInterface {
1717
this.autoUpdateAnchor = false;
1818
this.key = key;
1919
this.styles = null;
20+
this.disable_scroll = false;
2021
window.addEventListener("message", this.handleMessage.bind(this));
2122
}
2223

2324
register(component, autoUpdateAnchor, emphasisStyle) {
2425
this.component = component;
2526
this.autoUpdateAnchor = autoUpdateAnchor;
2627
this.emphasisStyle = emphasisStyle;
27-
console.debug('Registered component', component, autoUpdateAnchor);
28+
console.debug('Registered component for key ', this.key, ": ", component, autoUpdateAnchor);
2829
}
2930

3031
//Styles from ScrollNavigationBar.tsx
31-
updateStyles(styles) {
32+
updateConfig(styles, disable_scroll) {
3233
this.styles = styles;
33-
console.debug('Updated styles', styles);
34+
this.disable_scroll = disable_scroll;
35+
console.debug('Updated config', styles, disable_scroll);
3436
}
3537

3638
scroll(anchorId) {
3739
const element = document.getElementById(anchorId);
3840
console.debug('Scrolling to', anchorId);
3941
if (element) {
40-
element.scrollIntoView({ behavior: 'smooth' , block: 'start'});
42+
//Apply smooth or instant scrolling
43+
const behavior = this.disable_scroll ? 'instant' : 'smooth';
44+
element.scrollIntoView({ behavior , block: 'start'});
4145
}
4246
this.emphasize(anchorId);
4347
}
@@ -208,17 +212,17 @@ class CrossOriginInterface {
208212
this.register(event.source, auto_update_anchor, emphasis_style);
209213
}
210214
else {
211-
console.error('Must register component with this CrossOriginInterface before calling other methods');
215+
console.error('Must register component with this CrossOriginInterface before calling other methods', event.data);
212216
}
213217
}
214218

215219
switch (COI_method) {
216220
case 'register':
217221
console.debug('Register can only be called once per key.');
218222
break;
219-
case 'updateStyles':
220-
const {styles} = event.data;
221-
this.updateStyles(styles);
223+
case 'updateConfig':
224+
const {styles, disable_scroll} = event.data;
225+
this.updateConfig(styles, disable_scroll);
222226
break;
223227
case 'scroll':
224228
const { anchor_id: scrollAnchorId } = event.data;

streamlit_scroll_navigation/frontend/src/ScrollNavigationBar.tsx

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ interface State {
1515
//It interfaces with the parent window via CrossOriginInterface.js (COI)
1616
class ScrollNavigationBar extends StreamlitComponentBase<State> {
1717
public state = { activeAnchorId: "" };
18+
private disable_scroll: boolean = false;
19+
private mounted = false;
1820

1921
// Send message to COI
2022
postMessage(COI_method: string,
@@ -23,38 +25,37 @@ class ScrollNavigationBar extends StreamlitComponentBase<State> {
2325
anchor_ids?:string[];
2426
auto_update_anchor?: boolean
2527
styles?: { [key: string]: CSSProperties }
26-
}): void {
28+
disable_scroll?: boolean
29+
}): boolean {
2730
const { key } = this.props.args;
2831
if (key == null || typeof key !== "string") {
2932
throw new Error("Invalid key: key must be a string.");
3033
}
34+
window.parent.postMessage({ COI_method, key, ...data }, "*");
3135

32-
const { anchor_id, anchor_ids, styles} = data || {};
33-
window.parent.postMessage({ COI_method, key, anchor_id, anchor_ids, styles}, "*");
36+
console.debug("postMessage from ", key, ": ", COI_method, data);
37+
return true;
3438
}
3539

3640

3741
postRegister(auto_update_anchor:boolean): void {
3842
this.postMessage("register", { auto_update_anchor } );
39-
console.debug("postRegister");
4043
}
41-
postUpdateStyles(styles:{[key:string] : CSSProperties} ): void {
42-
this.postMessage("updateStyles", {styles} );
43-
console.debug("postUpdateStyles", styles);
44+
postUpdateConfig(): void {
45+
let styles = this.styles;
46+
let disable_scroll = this.disable_scroll;
47+
this.postMessage("updateConfig", {styles, disable_scroll} );
4448
}
4549
postScroll(anchor_id: string): void {
4650
this.postMessage("scroll", { anchor_id });
47-
console.debug("postScroll", anchor_id);
4851
}
4952
postTrackAnchors(anchor_ids: string[]): void {
5053
this.postMessage("trackAnchors", { anchor_ids });
51-
console.debug("postTrackAnchors", anchor_ids);
5254
}
5355
postUpdateActiveAnchor(anchor_id: string): void {
5456
const { auto_update_anchor } = this.getCleanedArgs();
5557
if (auto_update_anchor)
5658
this.postMessage("updateActiveAnchor", { anchor_id });
57-
console.debug("postUpdateActiveAnchor", anchor_id, "; autoupdate", auto_update_anchor);
5859
}
5960

6061
// Handle menu item click
@@ -71,13 +72,15 @@ class ScrollNavigationBar extends StreamlitComponentBase<State> {
7172
};
7273

7374
public componentDidMount(): void {
74-
const { anchor_ids, auto_update_anchor} = this.getCleanedArgs();
75+
const { anchor_ids, auto_update_anchor, disable_scroll} = this.getCleanedArgs();
7576
const initialAnchorId = anchor_ids[0];
7677

78+
this.disable_scroll = disable_scroll;
79+
7780
// Register component
7881
this.postRegister(auto_update_anchor);
7982
// Send styles to COI
80-
this.postUpdateStyles(this.styles);
83+
this.postUpdateConfig();
8184
// Tell COI to track anchors for visibility
8285
this.postTrackAnchors(anchor_ids);
8386
// Set initial active anchor for component and COI
@@ -88,6 +91,8 @@ class ScrollNavigationBar extends StreamlitComponentBase<State> {
8891

8992
//Send component value to streamlit
9093
Streamlit.setComponentValue(initialAnchorId);
94+
95+
this.mounted = true;
9196
}
9297

9398
componentDidUpdate(): void {
@@ -128,8 +133,18 @@ class ScrollNavigationBar extends StreamlitComponentBase<State> {
128133
}
129134
}
130135

131-
private getCleanedArgs() {
132-
let { key, anchor_ids, anchor_labels, anchor_icons, force_anchor, orientation, override_styles, auto_update_anchor} = this.props.args;
136+
private getCleanedArgs(): {
137+
anchor_ids: string[],
138+
anchor_labels: string[],
139+
anchor_icons: string[],
140+
force_anchor: string,
141+
key: string,
142+
orientation: string,
143+
override_styles: { [key: string]: CSSProperties },
144+
auto_update_anchor: boolean,
145+
disable_scroll: boolean }
146+
{
147+
let { key, anchor_ids, anchor_labels, anchor_icons, force_anchor, orientation, override_styles, auto_update_anchor, disable_scroll} = this.props.args;
133148
//key is required
134149
if (key == null || typeof key !== "string") {
135150
throw new Error("Invalid key: key must be a string.");
@@ -203,7 +218,18 @@ class ScrollNavigationBar extends StreamlitComponentBase<State> {
203218
}
204219
}
205220

206-
return { anchor_ids, anchor_labels, anchor_icons, force_anchor, key, orientation, override_styles, auto_update_anchor };
221+
//disable_scroll is an optional boolean
222+
//If not provided, default to false
223+
//If provided, it must be a boolean
224+
if (disable_scroll == null) {
225+
disable_scroll = false;
226+
} else {
227+
if (typeof disable_scroll !== "boolean") {
228+
throw new Error("Invalid disable_scroll: disable_scroll must be a boolean.");
229+
}
230+
}
231+
232+
return { anchor_ids, anchor_labels, anchor_icons, force_anchor, key, orientation, override_styles, auto_update_anchor, disable_scroll};
207233
}
208234

209235
static getBiName(icon: string) {
@@ -268,30 +294,39 @@ class ScrollNavigationBar extends StreamlitComponentBase<State> {
268294

269295
// Render sidebar with dynamic orientation handling
270296
public render = (): ReactNode => {
271-
const { orientation, override_styles } = this.getCleanedArgs();
272-
273-
// Deep merge override_styles into styles
274-
const mergeDeep = (target: any, source: any) => {
275-
let changed = false;
276-
for (const key in source) {
277-
if (source[key] instanceof Object && key in target) {
278-
const { changed: childChanged } = mergeDeep(target[key], source[key]);
279-
if (childChanged) {
280-
changed = true;
281-
}
282-
} else {
283-
if (target[key] !== source[key]) {
284-
target[key] = source[key];
285-
changed = true;
297+
const { orientation, override_styles, disable_scroll} = this.getCleanedArgs();
298+
299+
if (this.mounted) {
300+
// Deep merge override_styles into styles
301+
const mergeDeep = (target: any, source: any) => {
302+
let changed = false;
303+
for (const key in source) {
304+
if (source[key] instanceof Object && key in target) {
305+
const { changed: childChanged } = mergeDeep(target[key], source[key]);
306+
if (childChanged) {
307+
changed = true;
308+
}
309+
} else {
310+
if (target[key] !== source[key]) {
311+
target[key] = source[key];
312+
changed = true;
313+
}
286314
}
287315
}
316+
return { result: target, changed };
317+
};
318+
let { changed } = mergeDeep(this.styles, override_styles);
319+
320+
// Update disable_scroll if it has changed
321+
if (disable_scroll !== this.disable_scroll) {
322+
this.disable_scroll = disable_scroll;
323+
changed = true;
324+
}
325+
326+
//Communicate new styles and other config to COI
327+
if (changed) {
328+
this.postUpdateConfig()
288329
}
289-
return { result: target, changed };
290-
};
291-
const { changed } = mergeDeep(this.styles, override_styles);
292-
//Communicate new styles to COI
293-
if (changed) {
294-
this.postUpdateStyles(this.styles);
295330
}
296331

297332
// Adjust layout direction based on orientation

0 commit comments

Comments
 (0)