feat(scroller): DLT-2624 add before/after/empty slots and scroll-end events - #1385
Conversation
|
Please add either the |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Central YAML (inherited), Workspace UI (inherited) Review profile: ASSERTIVE Plan: Enterprise Run ID: 📒 Files selected for processing (5)
🔗 Linked repositories identifiedCodeRabbit considers these linked repositories for cross-repo context during reviews:
Adds Overall Judgement: ✅ Ready to merge — The implementation and supporting tests and documentation cover the new infinite-scrolling API. WalkthroughChangesThe scroller now supports buffer-relative Scroller infinite-scroll support
Sequence Diagram(s)sequenceDiagram
actor User
participant CoreScroller
participant DtScroller
participant Parent
User->>CoreScroller: Scroll within the configured buffer
CoreScroller->>DtScroller: Emit scroll-start or scroll-end
DtScroller->>Parent: Forward scroll-start or scroll-end
Possibly related PRs
Suggested reviewers: ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
✔️ Deploy previews ready! |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/dialtone-documentation/docs/components/scroller.md`:
- Around line 237-244: Update fetchNextPage to return immediately when
isFetching.value is already true, preventing concurrent pagination requests.
Wrap the fetchMore and item-update flow in try/finally so isFetching.value is
reset to false on both success and rejection, while preserving the existing
nextTick and scroller.value.updateItems behavior after a successful fetch.
- Around line 200-206: Update the Infinite Scroll section in the Scroller
documentation to describe the before slot as content rendered before the items
that scrolls with the list, optionally including a concise usage example.
In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue`:
- Around line 54-56: Render the empty slot only when items is empty by adding
the !items.length guard in core_scroller.vue. Update
packages/dialtone-vue/components/scroller/scroller.test.js lines 211-216 to
mount with an empty item list and retain one assertion confirming the empty
content renders.
- Around line 12-18: The normal-flow before slot is not accounted for by the
virtual scroll calculations, causing incorrect visibility when it has height.
Update the scroller’s _getScroll() and _updateVisibleItems() logic to measure
and apply the before-slot offset, or render the slot outside the virtual
coordinate system, while preserving correct item visibility; add a regression
test using a nonzero-height before slot.
In `@packages/dialtone-vue/components/scroller/scroller_default.story.vue`:
- Around line 137-140: Update the loading indicator styling around the .loading
class to remove inline component CSS, replacing it with Dialtone utility classes
or a tokenized style defined in packages/dialtone-css. Replace the raw `#666`
color and hardcoded pixel padding with the appropriate Dialtone color and
spacing tokens while preserving the italic loading appearance.
In `@packages/dialtone-vue/components/scroller/scroller.test.js`:
- Around line 95-98: Split the multi-assertion tests in scroller.test.js so each
test contains exactly one assertion: lines 95-98 should separately verify the
public buffer prop and CoreScroller forwarding; lines 149-156 should separately
verify the precondition and event emission; lines 197-202 and 204-209 should
separately verify component existence and wrapper placement. Update the relevant
scroller test cases without changing their behaviors.
In `@packages/dialtone-vue/components/scroller/scroller.vue`:
- Around line 68-76: Update the buffer prop definition in Scroller to validate
finite, non-negative numbers, then normalize any invalid runtime value to the
default before passing it to CoreScroller. Ensure negative and non-finite values
cannot affect the computed render range, while valid buffer values retain their
existing behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited), Workspace UI (inherited)
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 423bdaca-882b-4403-8158-9abc4c55ccfc
📒 Files selected for processing (7)
apps/dialtone-documentation/docs/components/scroller.mdpackages/dialtone-vue/components/scroller/modules/core_scroller.vuepackages/dialtone-vue/components/scroller/modules/dynamic_scroller.vuepackages/dialtone-vue/components/scroller/scroller.stories.jspackages/dialtone-vue/components/scroller/scroller.test.jspackages/dialtone-vue/components/scroller/scroller.vuepackages/dialtone-vue/components/scroller/scroller_default.story.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
dialpad/ios(manual)dialpad/firespotter(manual)dialpad/semantic-release-changelog-json(auto-detected)dialpad/conventional-changelog-angular(auto-detected)
| it('declares buffer as its own prop and passes it to the underlying scroller', () => { | ||
| expect(wrapper.props('buffer')).toBe(500); | ||
| expect(wrapper.findComponent(CoreScroller).props('buffer')).toBe(500); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Split these tests to one assertion each.
packages/dialtone-vue/components/scroller/scroller.test.js#L95-L98: Separate public prop declaration from CoreScroller forwarding.packages/dialtone-vue/components/scroller/scroller.test.js#L149-L156: Separate the precondition from event emission.packages/dialtone-vue/components/scroller/scroller.test.js#L197-L202: Assert existence and wrapper placement in separate tests.packages/dialtone-vue/components/scroller/scroller.test.js#L204-L209: Assert existence and wrapper placement in separate tests.
As per coding guidelines, **/*.test.{js,ts} requires one assertion per test.
📍 Affects 1 file
packages/dialtone-vue/components/scroller/scroller.test.js#L95-L98(this comment)packages/dialtone-vue/components/scroller/scroller.test.js#L149-L156packages/dialtone-vue/components/scroller/scroller.test.js#L197-L202packages/dialtone-vue/components/scroller/scroller.test.js#L204-L209
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dialtone-vue/components/scroller/scroller.test.js` around lines 95 -
98, Split the multi-assertion tests in scroller.test.js so each test contains
exactly one assertion: lines 95-98 should separately verify the public buffer
prop and CoreScroller forwarding; lines 149-156 should separately verify the
precondition and event emission; lines 197-202 and 204-209 should separately
verify component existence and wrapper placement. Update the relevant scroller
test cases without changing their behaviors.
Sources: Coding guidelines, Path instructions
|
Ignacio Ropolo (@iropolo) you probably want to take a look at this one since you wrote it initially |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/dialtone-vue/components/scroller/modules/core_scroller.vue (2)
12-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAccount for the leading slot in
scrollToItem()
DtScrollerpublicly exposesscrollToItem(), but the method uses the item-wrapper coordinate. A non-emptybeforeslot shifts the wrapper, so the target is offset by the slot size. Add this offset for fixed and dynamic item sizes, and add a regression test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue` around lines 12 - 20, Update DtScroller’s scrollToItem() calculations to include the rendered before slot’s height/offset when determining the target position, for both fixed-size and dynamic-size item layouts. Preserve existing behavior when the slot is absent or empty, and add a regression test covering a non-empty before slot and scrollToItem().
544-547: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winFinalize scroller state before emitting boundary events.
If a boundary handler changes
itemsand callsupdateItems()synchronously, re-entry occurs beforeview.position,startIndex, andendIndexare assigned. The outer loop can then resume with a changed item list and pool. Record boundary flags and emit after these assignments, or defer re-entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue` around lines 544 - 547, Update the scroller logic around the boundary checks in the newlyUsedView handling to record scroll-start and scroll-end flags instead of emitting immediately. Assign view.position, startIndex, and endIndex first, then emit the deferred boundary events so synchronous updateItems() calls cannot re-enter with incomplete state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue`:
- Around line 361-366: Update the buffered bounds calculation in the scroller’s
scroll-window logic to subtract the measured leading-slot size from both
scroll.start and scroll.end. Remove the trailing-slot addition to scroll.end,
while preserving the existing direction-specific slotSize measurement.
In `@packages/dialtone-vue/components/scroller/scroller.test.js`:
- Around line 252-256: Update the scroller test around firstItem to add a stable
data-qa attribute to the recycled item and select it using that attribute
instead of the .vue-recycle-scroller__item-view class. Preserve the existing
User 0 selection and transform assertion.
---
Outside diff comments:
In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue`:
- Around line 12-20: Update DtScroller’s scrollToItem() calculations to include
the rendered before slot’s height/offset when determining the target position,
for both fixed-size and dynamic-size item layouts. Preserve existing behavior
when the slot is absent or empty, and add a regression test covering a non-empty
before slot and scrollToItem().
- Around line 544-547: Update the scroller logic around the boundary checks in
the newlyUsedView handling to record scroll-start and scroll-end flags instead
of emitting immediately. Assign view.position, startIndex, and endIndex first,
then emit the deferred boundary events so synchronous updateItems() calls cannot
re-enter with incomplete state.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited), Workspace UI (inherited)
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 94f57528-8799-4a21-89e8-eb329f8f5ff5
📒 Files selected for processing (4)
apps/dialtone-documentation/docs/components/scroller.mdpackages/dialtone-vue/components/scroller/modules/core_scroller.vuepackages/dialtone-vue/components/scroller/scroller.test.jspackages/dialtone-vue/components/scroller/scroller_default.story.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
dialpad/ios(manual)dialpad/firespotter(manual)dialpad/semantic-release-changelog-json(auto-detected)dialpad/conventional-changelog-angular(auto-detected)
| // The leading and trailing slots sit in normal flow inside the viewport, so the item | ||
| // wrapper does not start at offset 0. Widen the window by their measured size. | ||
| const slotSize = props.direction === 'vertical' ? 'scrollHeight' : 'scrollWidth'; | ||
| if (before.value) scroll.start -= before.value[slotSize]; | ||
| if (after.value) scroll.end += after.value[slotSize]; | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Use the leading slot offset for both buffered bounds.
The item wrapper starts after before, so both scroll.start and scroll.end must subtract the before size. Adding after to scroll.end expands the item window beyond the viewport. A tall after slot can activate the final item too early, emit scroll-end before the documented threshold, and hit the 1000-view guard.
Proposed fix
- if (before.value) scroll.start -= before.value[slotSize];
- if (after.value) scroll.end += after.value[slotSize];
+ const beforeSize = before.value?.[slotSize] ?? 0;
+ scroll.start -= beforeSize;
+ scroll.end -= beforeSize;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // The leading and trailing slots sit in normal flow inside the viewport, so the item | |
| // wrapper does not start at offset 0. Widen the window by their measured size. | |
| const slotSize = props.direction === 'vertical' ? 'scrollHeight' : 'scrollWidth'; | |
| if (before.value) scroll.start -= before.value[slotSize]; | |
| if (after.value) scroll.end += after.value[slotSize]; | |
| // The leading and trailing slots sit in normal flow inside the viewport, so the item | |
| // wrapper does not start at offset 0. Widen the window by their measured size. | |
| const slotSize = props.direction === 'vertical' ? 'scrollHeight' : 'scrollWidth'; | |
| const beforeSize = before.value?.[slotSize] ?? 0; | |
| scroll.start -= beforeSize; | |
| scroll.end -= beforeSize; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue` around
lines 361 - 366, Update the buffered bounds calculation in the scroller’s
scroll-window logic to subtract the measured leading-slot size from both
scroll.start and scroll.end. Remove the trailing-slot addition to scroll.end,
while preserving the existing direction-specific slotSize measurement.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (4)
packages/dialtone-vue/components/scroller/scroller.test.js (1)
95-98: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse one assertion in each test.
Split each changed test that has multiple assertions. Keep setup or event preconditions in separate tests where needed.
Also applies to: 149-156, 197-216, 243-256
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dialtone-vue/components/scroller/scroller.test.js` around lines 95 - 98, Split the affected tests in the scroller test suite, including the test around buffer prop forwarding and the additional ranges noted, so each test contains exactly one assertion. Preserve the existing setup, event preconditions, and expected values, moving related assertions into separate clearly named tests.Sources: Coding guidelines, Path instructions
packages/dialtone-vue/components/scroller/modules/core_scroller.vue (2)
55-57: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRender
emptyonly whenitemsis empty.The
emptyslot renders with populated item views. This displays empty-state content and list content together.
packages/dialtone-vue/components/scroller/modules/core_scroller.vue#L55-L57: Guard the slot withv-if="!items.length".packages/dialtone-vue/components/scroller/scroller.test.js#L211-L216: Mount with an emptyitemsarray and assert the empty-state output.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue` around lines 55 - 57, Guard the empty slot in core_scroller.vue with !items.length so it renders only when no items exist. Add or update the scroller test in packages/dialtone-vue/components/scroller/scroller.test.js at lines 211-216 to mount with an empty items array and assert the empty-state output.
361-365: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMap both virtual bounds into the item-wrapper coordinate system.
beforeshifts the item wrapper. Subtract its size from bothscroll.startandscroll.end. Do not add theaftersize because it does not shift item coordinates.
packages/dialtone-vue/components/scroller/modules/core_scroller.vue#L361-L365: Subtract the leading-slot size from both buffered bounds and remove the trailing-slot adjustment.packages/dialtone-vue/components/scroller/scroller.test.js#L225-L257: Mock a distinct leading-slot size and assert the expected item range after coordinate conversion.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue` around lines 361 - 365, Update the coordinate conversion in packages/dialtone-vue/components/scroller/modules/core_scroller.vue#L361-L365 to subtract the measured leading-slot size from both scroll.start and scroll.end, and remove the trailing-slot adjustment. In packages/dialtone-vue/components/scroller/scroller.test.js#L225-L257, mock a distinct leading-slot size and assert the resulting item range uses both converted bounds.packages/dialtone-vue/components/scroller/scroller.vue (1)
68-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winNormalize invalid
buffervalues before forwarding them.
bufferaccepts negative and non-finite numbers. CoreScroller uses the value in render-range arithmetic. This can create invalid ranges or hit the 1,000-item guard.A Vue prop validator only produces a development warning. It does not replace the supplied value. (vuejs.org) Add a finite, non-negative
validatorand forward a normalized fallback of200.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/dialtone-vue/components/scroller/scroller.vue` around lines 68 - 76, Update the buffer prop definition in Scroller to validate only finite, non-negative numbers, then normalize invalid supplied values to 200 before passing buffer into CoreScroller. Preserve the existing default of 200 for omitted values and ensure CoreScroller never receives negative or non-finite buffer values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/dialtone-vue/components/scroller/scroller.stories.js`:
- Around line 108-123: Update the descriptions for the scroll-start and
scroll-end event entries in the Scroller stories to use direction-neutral
“viewport start” and “viewport end” edge wording instead of “top” and “bottom,”
while preserving the existing buffer-length behavior and append-items guidance.
In `@packages/dialtone-vue/components/scroller/scroller.test.js`:
- Around line 149-172: Update both boundary-event tests around handleScroll to
be async, await each wrapper.trigger('scroll') call, and await one
animation-frame tick before asserting emitted scroll-end or scroll-start events.
Preserve the existing scroll positions and assertion logic.
- Around line 197-221: The scroller slot and empty-item tests currently rely on
styling classes; update the affected tests, including the referenced ranges, to
query stable data-qa markers for before/after slot wrappers and recycled item
wrappers. Replace classList assertions with data-qa-based checks while
preserving the existing rendering expectations and no-wrapper behavior.
---
Duplicate comments:
In `@packages/dialtone-vue/components/scroller/modules/core_scroller.vue`:
- Around line 55-57: Guard the empty slot in core_scroller.vue with
!items.length so it renders only when no items exist. Add or update the scroller
test in packages/dialtone-vue/components/scroller/scroller.test.js at lines
211-216 to mount with an empty items array and assert the empty-state output.
- Around line 361-365: Update the coordinate conversion in
packages/dialtone-vue/components/scroller/modules/core_scroller.vue#L361-L365 to
subtract the measured leading-slot size from both scroll.start and scroll.end,
and remove the trailing-slot adjustment. In
packages/dialtone-vue/components/scroller/scroller.test.js#L225-L257, mock a
distinct leading-slot size and assert the resulting item range uses both
converted bounds.
In `@packages/dialtone-vue/components/scroller/scroller.test.js`:
- Around line 95-98: Split the affected tests in the scroller test suite,
including the test around buffer prop forwarding and the additional ranges
noted, so each test contains exactly one assertion. Preserve the existing setup,
event preconditions, and expected values, moving related assertions into
separate clearly named tests.
In `@packages/dialtone-vue/components/scroller/scroller.vue`:
- Around line 68-76: Update the buffer prop definition in Scroller to validate
only finite, non-negative numbers, then normalize invalid supplied values to 200
before passing buffer into CoreScroller. Preserve the existing default of 200
for omitted values and ensure CoreScroller never receives negative or non-finite
buffer values.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Central YAML (inherited), Workspace UI (inherited)
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 94f57528-8799-4a21-89e8-eb329f8f5ff5
📒 Files selected for processing (7)
apps/dialtone-documentation/docs/components/scroller.mdpackages/dialtone-vue/components/scroller/modules/core_scroller.vuepackages/dialtone-vue/components/scroller/modules/dynamic_scroller.vuepackages/dialtone-vue/components/scroller/scroller.stories.jspackages/dialtone-vue/components/scroller/scroller.test.jspackages/dialtone-vue/components/scroller/scroller.vuepackages/dialtone-vue/components/scroller/scroller_default.story.vue
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
dialpad/ios(manual)dialpad/firespotter(manual)dialpad/semantic-release-changelog-json(auto-detected)dialpad/conventional-changelog-angular(auto-detected)
| it('`scroll-end` when the last item enters the render pool', () => { | ||
| expect(wrapper.emitted()['scroll-end']).toBeUndefined(); | ||
|
|
||
| defaultContent.element.scrollTop = | ||
| defaultContent.element.scrollHeight - defaultContent.element.clientHeight; | ||
| wrapper.trigger('scroll'); | ||
|
|
||
| expect(wrapper.emitted()['scroll-end']).toBeTruthy(); | ||
| }); | ||
|
|
||
| it('`scroll-start` when the first item re-enters the render pool', () => { | ||
| // 240 is chosen so the window starts at index 1 (240 - buffer 200 = 40, over | ||
| // itemSize 30) while still overlapping the mount window — the algorithm only | ||
| // releases off-window views on a continuous move, and a view must be released | ||
| // before it can count as newly used again. | ||
| defaultContent.element.scrollTop = 240; | ||
| wrapper.trigger('scroll'); | ||
|
|
||
| const beforeReturn = wrapper.emitted()['scroll-start'].length; | ||
|
|
||
| defaultContent.element.scrollTop = 0; | ||
| wrapper.trigger('scroll'); | ||
|
|
||
| expect(wrapper.emitted()['scroll-start'].length).toBeGreaterThan(beforeReturn); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Wait for the scheduled render update before asserting events.
handleScroll() updates the pool in requestAnimationFrame(). These assertions run before that callback can emit either boundary event. Make both tests async, await wrapper.trigger('scroll'), then await one animation frame before asserting.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/dialtone-vue/components/scroller/scroller.test.js` around lines 149
- 172, Update both boundary-event tests around handleScroll to be async, await
each wrapper.trigger('scroll') call, and await one animation-frame tick before
asserting emitted scroll-end or scroll-start events. Preserve the existing
scroll positions and assertion logic.
Brad Paugh (braddialpad)
left a comment
There was a problem hiding this comment.
"Restoring the upstream items watcher in core_scroller.vue would remove the need for consumers to call updateItems() at all. It was deliberately left out of scope here: it is a behaviour change to a shared component and deserves its own ticket. The commented-out original shows why it was disabled — prepending and appending need different recompute calls to avoid the scroll position jumping, and only the caller knows which happened."
I've generated a ticket for this: https://dialpad.atlassian.net/browse/DLT-3563
Solid PR, works really well, clean code
Ignacio Ropolo (iropolo)
left a comment
There was a problem hiding this comment.
Looks good, thanks!!
# [3.224.0](dialtone-vue/v3.223.2...dialtone-vue/v3.224.0) (2026-08-07) ### Features * **Scroller:** DLT-2624 add before/after/empty slots and scroll-end events ([#1385](#1385)) ([f2e1f86](f2e1f86))
# [9.190.0](dialtone/v9.189.1...dialtone/v9.190.0) (2026-08-07) ### Features * **Scroller:** DLT-2624 add before/after/empty slots and scroll-end events ([#1385](#1385)) ([f2e1f86](f2e1f86))
# [10.0.0-next.11](dialtone/v10.0.0-next.10...dialtone/v10.0.0-next.11) (2026-08-10) ### Bug Fixes * **Avatar:** DP-200415 prevent presence indicators from clipping avatar content ([#1387](#1387)) ([e693f80](e693f80)) * **Message Input:** DP-200077 disable text-align when rich-text is false ([#1384](#1384)) ([6474c8e](6474c8e)) ### Features * **Icons:** DLT-3553 add fax-inbound and fax-outbound icons ([#1378](#1378)) ([46538f6](46538f6)) * **Scroller:** DLT-2624 add before/after/empty slots and scroll-end events ([#1385](#1385)) ([f2e1f86](f2e1f86))
Obligatory GIF (super important!)
🛠️ Type Of Change
These types will increment the version number on release:
📖 Jira Ticket
https://dialpad.atlassian.net/browse/DLT-2624
📖 Description
Adds the slots and events
DtScrollerwas missing, so consumers can build infinite-scrolling lists on it.before,afterandemptyslots. Positioned exactly as upstreamvue-virtual-scrollerplaces them:beforeandafterrender inside a.vue-recycle-scroller__slotwrapper as siblings of the item wrapper,emptyrenders inside the wrapper after the items. The CSS for those wrappers already shipped indialtone-css, so there is no CSS change here. Every forward is guarded byv-if="$slots.<name>"at all three layers, so no empty wrapper element renders when a consumer passes nothing.scroll-endandscroll-startevents, restored from the vendored upstream code that had been commented out incore_scroller.vue. They fire when the last or first item enters the rendered view pool — onebufferlength before the viewport edge — so a consumer can prefetch before the user runs out of content.user-positionis unchanged.bufferpromoted to a declared prop. It previously reachedCoreScrolleronly through attribute fallthrough: functional, but untyped, undocumented, and liable to break silently if anyone addedinheritAttrs: false.argTypesentries for the new slots and events, and thebuffercontrol is now actually bound in the Default story (it was previously declared but inert).💡 Context
DtScrolleris a vendored fork ofvue-virtual-scrollerthat kept only thedefaultslot. Any consumer needing a loading indicator pinned to the end of a virtualised list — the standard infinite-scroll pattern — had no way to render one inside the scroll viewport, and no event telling it when to fetch. That left applications on the third-party library instead of the design system.The docs also now cover something that was previously undocumented and easy to trip over:
DtScrollerrecomputes its rendered window only on mount and on scroll, so it does not react toitemschanging on its own. Consumers have to callupdateItems()after appending a page, and should assign a new array rather than mutating in place. This has caught more than one consumer, so it is written down now with a worked example.📝 Checklist
For all PRs:
For all Vue changes:
The two accessibility boxes are deliberately unticked — see Next Steps.
🔮 Next Steps
itemswatcher incore_scroller.vuewould remove the need for consumers to callupdateItems()at all. It was deliberately left out of scope here: it is a behaviour change to a shared component and deserves its own ticket. The commented-out original shows why it was disabled — prepending and appending need different recompute calls to avoid the scroll position jumping, and only the caller knows which happened.📷 Screenshots / GIFs
The Default story in Storybook covers this: it is fixed-height with 50 items, logs
scroll-start/scroll-endto the actions panel, and renders a loading indicator in theafterslot. Scrolling to the floor there exercises the whole path — event timing, slot placement, and that the indicator scrolls with the list rather than being pinned.🔗 Sources