Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scroll_index to Column #7206

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions examples/reference/layouts/Column.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"\n",
"* **``objects``** (list): The list of objects to display in the Column, should not generally be modified directly except when replaced in its entirety.\n",
"* **``scroll``** (boolean): Enable scrollbars if the content overflows the size of the container.\n",
"* **``scroll_index``** (int): The index of the object to scroll to. If set the Column will scroll to the object at the specified index and will reset back to None after scrolling.\n",
"* **``scroll_position``** (int): Current scroll position of the Column. Setting this value will update the scroll position of the Column. Setting to 0 will scroll to the top.\n",
"* **``auto_scroll_limit``** (int): Max pixel distance from the latest object in the Column to activate automatic scrolling upon update. Setting to 0 disables auto-scrolling\n",
"* **``scroll_button_threshold``** (int): Min pixel distance from the latest object in the Column to display the scroll button. Setting to 0 disables the scroll button.\n",
Expand Down
10 changes: 10 additions & 0 deletions panel/layout/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,12 @@ class Column(ListPanel):
will update the scroll position of the Column. Setting to
0 will scroll to the top.""")

scroll_index = param.Integer(default=None, bounds=(0, None), doc="""
The index of the object to scroll to. If set the Column will
scroll to the object at the specified index and will reset
back to None after scrolling.""",
allow_None=True)

view_latest = param.Boolean(default=False, doc="""
Whether to scroll to the latest object on init. If not
enabled the view will be on the first object.""")
Expand Down Expand Up @@ -967,6 +973,10 @@ def _set_scrollable(self):
self.view_latest
)

@param.depends("scroll_index", watch=True)
def _reset_scroll_index(self):
self.scroll_index = None


class WidgetBox(ListPanel):
"""
Expand Down
35 changes: 33 additions & 2 deletions panel/models/column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,46 @@ export class ColumnView extends BkColumnView {
override connect_signals(): void {
super.connect_signals()

const {children, scroll_position, scroll_button_threshold} = this.model.properties
const {children, scroll_position, scroll_index, scroll_button_threshold} = this.model.properties

this.on_change(children, () => this.trigger_auto_scroll())
this.on_change(scroll_position, () => this.scroll_to_position())
this.on_change(scroll_index, () => this.scroll_to_index())
this.on_change(scroll_button_threshold, () => this.toggle_scroll_button())
}

get distance_from_latest(): number {
return this.el.scrollHeight - this.el.scrollTop - this.el.clientHeight
}

scroll_to_index(): void {
const index = this.model.scroll_index
if (index === null) {
return
}

if (index >= this.model.children.length) {
console.warn(`Invalid scroll index: ${index}`)
return
}

// Get the child view at the specified index
const childView = this.child_views[index]
if (!childView) {
console.warn(`Child view not found for index: ${index}`)
return
}

// Get the top position of the child element relative to the column
const childEl = childView.el
const childRect = childEl.getBoundingClientRect()
const columnRect = this.el.getBoundingClientRect()
const relativeTop = childRect.top - columnRect.top + this.el.scrollTop

// Scroll to the child's position
this.model.scroll_position = Math.round(relativeTop)
}

scroll_to_position(): void {
if (this._updating) {
return
Expand Down Expand Up @@ -106,6 +135,7 @@ export namespace Column {
export type Attrs = p.AttrsOf<Props>
export type Props = BkColumn.Props & {
scroll_position: p.Property<number>
scroll_index: p.Property<number | null>
auto_scroll_limit: p.Property<number>
scroll_button_threshold: p.Property<number>
view_latest: p.Property<boolean>
Expand All @@ -126,8 +156,9 @@ export class Column extends BkColumn {
static {
this.prototype.default_view = ColumnView

this.define<Column.Props>(({Int, Bool}) => ({
this.define<Column.Props>(({Int, Bool, Nullable}) => ({
scroll_position: [Int, 0],
scroll_index: [Nullable(Int), null],
auto_scroll_limit: [Int, 0],
scroll_button_threshold: [Int, 0],
view_latest: [Bool, false],
Expand Down
7 changes: 7 additions & 0 deletions panel/models/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class Column(BkColumn):
0 will scroll to the top."""
)

scroll_index = Nullable(
Int,
help="""
Index of the object to scroll to. Setting this value will
scroll the Column to the object at the given index."""
)

auto_scroll_limit = Int(
default=0,
help="""
Expand Down
30 changes: 30 additions & 0 deletions panel/tests/ui/layout/test_column.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,33 @@ def test_column_scroll_position_param_updated(page):

column = page.locator(".bk-panel-models-layout-Column")
expect(column).to_have_js_property('scrollTop', 175)


def test_column_scroll_index(page):
col = Column(
*list(range(100)),
height=300,
sizing_mode="fixed",
scroll=True,
)

serve_component(page, col)

page.wait_for_timeout(200)

# start at 0
column_el = page.locator(".bk-panel-models-layout-Column")
expect(column_el).to_have_js_property('scrollTop', 0)

# scroll to 50
col.scroll_index = 50
expect(column_el).to_have_js_property('scrollTop', 1362)
assert col.scroll_index is None

# scroll away using mouse wheel
column_el.evaluate('(el) => el.scrollTo({top: 1000})')
expect(column_el).to_have_js_property('scrollTop', 1000)

# scroll to 50 again
col.scroll_index = 50
expect(column_el).to_have_js_property('scrollTop', 1362)
Loading