Skip to content

Commit

Permalink
fix: RouteOnScroll helper could break with nested routers
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobrosenberg committed May 26, 2024
1 parent 58c3ea1 commit c9a3d55
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 20 deletions.
52 changes: 35 additions & 17 deletions lib/runtime/helpers/dedicated/RouteOnScroll/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { get } from 'svelte/store'
import { throttle } from '../../../../common/utils.js'
import { getRoutifyFragmentContext } from '../../../utils/index.js'
import { url } from '../../index.js'

/**
* @typedef {Object} RouteOnScrollOptions
Expand All @@ -18,6 +19,7 @@ import { getRoutifyFragmentContext } from '../../../utils/index.js'
* @property {RouterContext|RenderContext} context
* @property {(elems: HTMLElement[]) => HTMLElement} findFocusedElement
* @property {'scroll'|'scrollend'} scrollEvent
* @property {import('../../index.js').Url} $url urlHelper
*/

/**
Expand Down Expand Up @@ -64,6 +66,7 @@ const defaultOptions = {
context: undefined,
findFocusedElement: null,
scrollEvent: 'scroll',
$url: null,
}

/**
Expand All @@ -79,12 +82,16 @@ export class BaseRouteOnScroll {

direction

/** @type {HTMLElement} */
boundaryElem

/**
* RouteOnScroll2 detects when the user scrolls to a new inlined page and updates the router accordingly
* RouteOnScroll detects when the user scrolls to a new inlined page and updates the router accordingly
* @param {Partial<BaseRouteOnScrollOptions>} options
* */
constructor(options = {}) {
this.id = Symbol()
this.$url = options.$url
options = { ...defaultOptions, ...options }
this.coolOffTime = options.coolOffTime
this.throttleTime = options.throttleTime
Expand Down Expand Up @@ -133,35 +140,41 @@ export class BaseRouteOnScroll {
if (focusedElement) {
const contexts = focusedElement['__routify_meta'].renderContext
const context = contexts.anchor || contexts.parent
if (context.router.activeRoute.get() != context.route)
context.router.url.set(context.node.path, 'replaceState', true, {
if (context.router.activeRoute.get() != context.route) {
const url = this.$url(context.node.path)
context.router.url.set(url, 'replaceState', true, {
// we got to this route by scrolling manually, so we don't want the router to do any scrolling
dontScroll: true,
})
}
}
}

subscribe(run) {
if (!this.context) this.context = getRoutifyFragmentContext()
if (!this.$url) this.$url = get(url)

// create an event listener that will be called when the scroll event is triggered
const onScroll = () => this.onScrollThrottled()

/** @type {HTMLElement|null} */
let elem
run(_elem => {
elem = _elem
this.boundaryElem = _elem
// add the event listener to the scroll event
if (elem) elem.addEventListener(this.scrollEvent, onScroll, { capture: true })
if (this.boundaryElem)
this.boundaryElem.addEventListener(this.scrollEvent, onScroll, {
capture: true,
})
else addEventListener(this.scrollEvent, onScroll, { capture: true })
})

// return a function that will remove the event listener
return () => {
if (typeof window === 'undefined') return
if (elem)
elem.removeEventListener(this.scrollEvent, onScroll, { capture: true })
else removeEventListener(this.scrollEvent, onScroll, { capture: true })
if (this.boundaryElem)
this.boundaryElem.removeEventListener(this.scrollEvent, onScroll, {
capture: true,
})
else removeEventListener(this.scrollEvent, onScroll)
}
}
}
Expand All @@ -178,18 +191,23 @@ export class RouteOnScroll extends BaseRouteOnScroll {
this.strategy = options.strategy || 'lowestAboveThreshold'
}

getElementPos(element) {
const { top, left } = element.getBoundingClientRect()
return this.direction === 'both'
? top + left
: this.direction === 'vertical'
? top
: left
}

findFocusedElement(elems) {
let candidateElement
let candidateStartingPos = -Infinity

const boundaryElemPos = this.boundaryElem && this.getElementPos(this.boundaryElem)

for (const element of elems) {
const { top, left } = element.getBoundingClientRect()
const pos =
this.direction === 'both'
? top + left
: this.direction === 'vertical'
? top
: left
const pos = this.getElementPos(element) - (boundaryElemPos || 0)
// get the lowest element that's above the threshold
if (this.strategy === 'lowestAboveThreshold') {
if (pos <= this.threshold && pos >= candidateStartingPos) {
Expand Down
12 changes: 9 additions & 3 deletions typings/lib/runtime/helpers/dedicated/RouteOnScroll/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ export function getDescendantNodesElements(context?: (RouterContext | RenderCont
*/
export class BaseRouteOnScroll {
/**
* RouteOnScroll2 detects when the user scrolls to a new inlined page and updates the router accordingly
* RouteOnScroll detects when the user scrolls to a new inlined page and updates the router accordingly
* @param {import('../../index.js').Url} $url
* @param {Partial<BaseRouteOnScrollOptions>} options
* */
constructor(options?: Partial<BaseRouteOnScrollOptions>);
constructor($url: import('../../index.js').Url, options?: Partial<BaseRouteOnScrollOptions>);
/** @type {Route} */
lastRoute: Route;
/** @type {HTMLElement[]} */
elems: HTMLElement[];
direction: any;
/** @type {HTMLElement} */
boundaryElem: HTMLElement;
$url: import("../../index.js").Url;
id: symbol;
coolOffTime: number;
throttleTime: number;
Expand All @@ -37,12 +41,14 @@ export class BaseRouteOnScroll {
export class RouteOnScroll extends BaseRouteOnScroll {
/**
* RouteOnScroll detects when the user scrolls to a new inlined page and updates the router accordingly
* @param {import('../../index.js').Url} $url
* @param {Partial<BaseRouteOnScrollOptions & RouteOnScrollOptions>} options
* */
constructor(options?: Partial<BaseRouteOnScrollOptions & RouteOnScrollOptions>);
constructor($url: import('../../index.js').Url, options?: Partial<BaseRouteOnScrollOptions & RouteOnScrollOptions>);
threshold: number;
direction: "both" | "vertical" | "horizontal";
strategy: "lowestAboveThreshold" | "withinThreshold";
getElementPos(element: any): any;
findFocusedElement(elems: any): any;
}
export type RouteOnScrollOptions = {
Expand Down

0 comments on commit c9a3d55

Please sign in to comment.