7 changed files with 155 additions and 91 deletions
@ -0,0 +1,58 @@ |
|||
/** |
|||
* Handles the scroll behavior on route navigation |
|||
* |
|||
* @param {object} to Route object of next page |
|||
* @param {object} from Route object of previous page |
|||
* @param {object} savedPosition Used by popstate navigations |
|||
* @returns {(object|boolean)} Scroll position or `false` |
|||
*/ |
|||
// @ts-ignore
|
|||
export async function scrollBehavior(to, from, savedPosition) { |
|||
await scrollWaiter.wait(); |
|||
// Use predefined scroll behavior if defined, defaults to no scroll behavior
|
|||
const behavior = document.documentElement.style.scrollBehavior || 'auto'; |
|||
|
|||
// Returning the `savedPosition` (if available) will result in a native-like
|
|||
// behavior when navigating with back/forward buttons
|
|||
if (savedPosition) { |
|||
return { ...savedPosition, behavior }; |
|||
} |
|||
|
|||
// Scroll to anchor by returning the selector
|
|||
if (to.hash) { |
|||
return { el: decodeURI(to.hash), behavior }; |
|||
} |
|||
|
|||
// Check if any matched route config has meta that discourages scrolling to top
|
|||
if (to.matched.some((m: any) => m.meta.scrollToTop === false)) { |
|||
// Leave scroll as it is
|
|||
return false; |
|||
} |
|||
|
|||
// Always scroll to top
|
|||
return { left: 0, top: 0, behavior }; |
|||
} |
|||
|
|||
// see https://github.com/vuejs/vue-router-next/blob/master/playground/scrollWaiter.ts
|
|||
class ScrollQueue { |
|||
private resolve: (() => void) | null = null; |
|||
private promise: Promise<any> | null = null; |
|||
|
|||
add() { |
|||
this.promise = new Promise((resolve) => { |
|||
this.resolve = resolve; |
|||
}); |
|||
} |
|||
|
|||
flush() { |
|||
this.resolve && this.resolve(); |
|||
this.resolve = null; |
|||
this.promise = null; |
|||
} |
|||
|
|||
async wait() { |
|||
await this.promise; |
|||
} |
|||
} |
|||
|
|||
export const scrollWaiter = new ScrollQueue(); |
|||
@ -1,23 +0,0 @@ |
|||
// see https://github.com/vuejs/vue-router-next/blob/master/playground/scrollWaiter.ts
|
|||
class ScrollQueue { |
|||
private resolve: (() => void) | null = null; |
|||
private promise: Promise<any> | null = null; |
|||
|
|||
add() { |
|||
this.promise = new Promise((resolve) => { |
|||
this.resolve = resolve; |
|||
}); |
|||
} |
|||
|
|||
flush() { |
|||
this.resolve && this.resolve(); |
|||
this.resolve = null; |
|||
this.promise = null; |
|||
} |
|||
|
|||
async wait() { |
|||
await this.promise; |
|||
} |
|||
} |
|||
|
|||
export const scrollWaiter = new ScrollQueue(); |
|||
@ -1,33 +0,0 @@ |
|||
class EventHub { |
|||
private cache: { [key: string]: Array<(data: any) => void> } = {}; |
|||
on(eventName: string, fn: (data: any) => void) { |
|||
this.cache[eventName] = this.cache[eventName] || []; |
|||
this.cache[eventName].push(fn); |
|||
} |
|||
|
|||
once(eventName: string, fn: (data: any) => void) { |
|||
const decor = (...args: any[]) => { |
|||
fn && fn.apply(this, args); |
|||
this.off(eventName, decor); |
|||
}; |
|||
this.on(eventName, decor); |
|||
return this; |
|||
} |
|||
|
|||
emit(eventName: string, data?: any) { |
|||
if (this.cache[eventName] === undefined) return; |
|||
this.cache[eventName].forEach((fn) => fn(data)); |
|||
} |
|||
off(eventName: string, fn: (data: any) => void) { |
|||
if (this.cache[eventName] === undefined || this.cache[eventName].length === 0) return; |
|||
const i = this.cache[eventName].indexOf(fn); |
|||
if (i === -1) return; |
|||
this.cache[eventName].splice(i, 1); |
|||
} |
|||
|
|||
clear() { |
|||
this.cache = {}; |
|||
} |
|||
} |
|||
|
|||
export default EventHub; |
|||
@ -0,0 +1,73 @@ |
|||
/** |
|||
* Mitt: Tiny functional event emitter / pubsub |
|||
* |
|||
* @name mitt |
|||
* @param {Array} [all] Optional array of event names to registered handler functions |
|||
* @returns {Function} The function's instance |
|||
*/ |
|||
export default class Mitt { |
|||
private cache: Map<string, Array<(data: any) => void>>; |
|||
constructor(all = []) { |
|||
// A Map of event names to registered handler functions.
|
|||
this.cache = new Map(all); |
|||
} |
|||
|
|||
once(type: string, handler: Fn) { |
|||
const decor = (...args: any[]) => { |
|||
handler && handler.apply(this, args); |
|||
this.off(type, decor); |
|||
}; |
|||
this.on(type, decor); |
|||
return this; |
|||
} |
|||
|
|||
/** |
|||
* Register an event handler for the given type. |
|||
* |
|||
* @param {string|symbol} type Type of event to listen for, or `"*"` for all events |
|||
* @param {Function} handler Function to call in response to given event |
|||
*/ |
|||
on(type: string, handler: Fn) { |
|||
const handlers = this.cache.get(type); |
|||
const added = handlers && handlers.push(handler); |
|||
if (!added) { |
|||
this.cache.set(type, [handler]); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Remove an event handler for the given type. |
|||
* |
|||
* @param {string|symbol} type Type of event to unregister `handler` from, or `"*"` |
|||
* @param {Function} handler Handler function to remove |
|||
*/ |
|||
off(type: string, handler: Fn) { |
|||
const handlers = this.cache.get(type); |
|||
if (handlers) { |
|||
handlers.splice(handlers.indexOf(handler) >>> 0, 1); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Invoke all handlers for the given type. |
|||
* If present, `"*"` handlers are invoked after type-matched handlers. |
|||
* |
|||
* Note: Manually firing "*" handlers is not supported. |
|||
* |
|||
* @param {string|symbol} type The event type to invoke |
|||
* @param {*} [evt] Any value (object is recommended and powerful), passed to each handler |
|||
*/ |
|||
emit(type: string, evt: any) { |
|||
for (const handler of (this.cache.get(type) || []).slice()) handler(evt); |
|||
for (const handler of (this.cache.get('*') || []).slice()) handler(type, evt); |
|||
} |
|||
|
|||
/** |
|||
* Remove all event handlers. |
|||
* |
|||
* Note: This will also remove event handlers passed via `mitt(all: EventHandlerMap)`. |
|||
*/ |
|||
clear() { |
|||
this.cache.clear(); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue