mirror of
https://github.com/flarum/framework.git
synced 2025-03-15 00:05:12 +08:00
common: add utils abbreviateNumber, anchorScroll, Evented, ScrollListener
Evented is now a class instead of an object - it can be extended from a class now. Object.assign can still be used with it, but instead of `evented` with `Evented.prototype`
This commit is contained in:
parent
4f79a05a4b
commit
8ba86f9c5e
81
js/src/common/utils/Evented.ts
Normal file
81
js/src/common/utils/Evented.ts
Normal file
@ -0,0 +1,81 @@
|
||||
export type EventHandler = (...args: any) => any;
|
||||
|
||||
export default class Evented {
|
||||
/**
|
||||
* Arrays of registered event handlers, grouped by the event name.
|
||||
*/
|
||||
protected handlers: { [key: string]: EventHandler[] } = {};
|
||||
|
||||
/**
|
||||
* Get all of the registered handlers for an event.
|
||||
*
|
||||
* @param event The name of the event.
|
||||
*/
|
||||
protected getHandlers(event: string): EventHandler[] {
|
||||
this.handlers = this.handlers || {};
|
||||
|
||||
this.handlers[event] = this.handlers[event] || [];
|
||||
|
||||
return this.handlers[event];
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event.
|
||||
*
|
||||
* @param event The name of the event.
|
||||
* @param args Arguments to pass to event handlers.
|
||||
*/
|
||||
public trigger(event: string, ...args: any): this {
|
||||
this.getHandlers(event).forEach(handler => handler.apply(this, args));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event handler.
|
||||
*
|
||||
* @param event The name of the event.
|
||||
* @param handler The function to handle the event.
|
||||
*/
|
||||
on(event: string, handler: EventHandler): this {
|
||||
this.getHandlers(event).push(handler);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an event handler so that it will run only once, and then
|
||||
* unregister itself.
|
||||
*
|
||||
* @param event The name of the event.
|
||||
* @param handler The function to handle the event.
|
||||
*/
|
||||
one(event: string, handler: EventHandler): this {
|
||||
const wrapper = function() {
|
||||
handler.apply(this, arguments);
|
||||
|
||||
this.off(event, wrapper);
|
||||
};
|
||||
|
||||
this.getHandlers(event).push(wrapper);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister an event handler.
|
||||
*
|
||||
* @param event The name of the event.
|
||||
* @param handler The function that handles the event.
|
||||
*/
|
||||
off(event: string, handler: EventHandler): this {
|
||||
const handlers = this.getHandlers(event);
|
||||
const index = handlers.indexOf(handler);
|
||||
|
||||
if (index !== -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
70
js/src/common/utils/ScrollListener.ts
Normal file
70
js/src/common/utils/ScrollListener.ts
Normal file
@ -0,0 +1,70 @@
|
||||
const later =
|
||||
window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
(callback => window.setTimeout(callback, 1000 / 60));
|
||||
|
||||
/**
|
||||
* The `ScrollListener` class sets up a listener that handles window scroll
|
||||
* events.
|
||||
*/
|
||||
export default class ScrollListener {
|
||||
ticking: boolean = false;
|
||||
|
||||
callback: Function;
|
||||
active: EventListener;
|
||||
|
||||
/**
|
||||
* @param callback The callback to run when the scroll position
|
||||
* changes.
|
||||
*/
|
||||
public constructor(callback: Function) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* On each animation frame, as long as the listener is active, run the
|
||||
* `update` method.
|
||||
*/
|
||||
protected loop() {
|
||||
// THROTTLE: If the callback is still running (or hasn't yet run), we ignore
|
||||
// further scroll events.
|
||||
if (this.ticking) return;
|
||||
|
||||
// Schedule the callback to be executed soon (TM), and stop throttling once
|
||||
// the callback is done.
|
||||
later(() => {
|
||||
this.update();
|
||||
this.ticking = false;
|
||||
});
|
||||
|
||||
this.ticking = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the callback, whether there was a scroll event or not.
|
||||
*/
|
||||
public update() {
|
||||
this.callback(window.pageYOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening to and handling the window's scroll position.
|
||||
*/
|
||||
public start() {
|
||||
if (!this.active) {
|
||||
window.addEventListener('scroll', (this.active = this.loop.bind(this)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening to and handling the window's scroll position.
|
||||
*/
|
||||
public stop() {
|
||||
window.removeEventListener('scroll', this.active);
|
||||
|
||||
this.active = null;
|
||||
}
|
||||
}
|
17
js/src/common/utils/abbreviateNumber.tsx
Normal file
17
js/src/common/utils/abbreviateNumber.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* The `abbreviateNumber` utility converts a number to a shorter localized form.
|
||||
*
|
||||
* @example
|
||||
* abbreviateNumber(1234);
|
||||
* // "1.2K"
|
||||
*/
|
||||
export default (number: number): string => {
|
||||
// TODO: translation
|
||||
if (number >= 1000000) {
|
||||
return Math.floor(number / 1000000) + app.translator.transText('core.lib.number_suffix.mega_text');
|
||||
} else if (number >= 1000) {
|
||||
return Math.floor(number / 1000) + app.translator.transText('core.lib.number_suffix.kilo_text');
|
||||
} else {
|
||||
return number.toString();
|
||||
}
|
||||
};
|
27
js/src/common/utils/anchorScroll.ts
Normal file
27
js/src/common/utils/anchorScroll.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* The `anchorScroll` utility saves the scroll position relative to an element,
|
||||
* and then restores it after a callback has been run.
|
||||
*
|
||||
* This is useful if a redraw will change the page's content above the viewport.
|
||||
* Normally doing this will result in the content in the viewport being pushed
|
||||
* down or pulled up. By wrapping the redraw with this utility, the scroll
|
||||
* position can be anchor to an element that is in or below the viewport, so
|
||||
* the content in the viewport will stay the same.
|
||||
*
|
||||
* @param element The element to anchor the scroll position to.
|
||||
* @param callback The callback to run that will change page content.
|
||||
*/
|
||||
export default function anchorScroll(element: Element, callback: Function) {
|
||||
const $window = $(window);
|
||||
const $el = $(element);
|
||||
|
||||
if (!element || !$el.length) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
const relativeScroll = $el.offset().top - $window.scrollTop();
|
||||
|
||||
callback();
|
||||
|
||||
$window.scrollTop($el.offset().top - relativeScroll);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user