lisn.js
Version:
Simply handle user gestures and actions. Includes widgets.
231 lines (226 loc) • 8.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.wrapCallback = exports.Callback = void 0;
var MC = _interopRequireWildcard(require("../globals/minification-constants.cjs"));
var MH = _interopRequireWildcard(require("../globals/minification-helpers.cjs"));
var _tasks = require("../utils/tasks.cjs");
var _debug = _interopRequireDefault(require("../debug/debug.cjs"));
var _Callback;
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /**
* @module Modules/Callback
*/
/**
* @typeParam Args See {@link Callback}
*/
/**
* For minification optimization. Exposed through Callback.wrap.
*
* @ignore
* @internal
*/
const wrapCallback = (handlerOrCallback, debounceWindow = 0) => {
const isFunction = MH.isFunction(handlerOrCallback);
let isRemoved = () => false;
if (isFunction) {
// check if it's an invoke method
const callback = callablesMap.get(handlerOrCallback);
if (callback) {
return wrapCallback(callback);
}
} else {
isRemoved = handlerOrCallback.isRemoved;
}
const handler = isFunction ? handlerOrCallback : (...args) => handlerOrCallback.invoke(...args);
const wrapper = new Callback((0, _tasks.getDebouncedHandler)(debounceWindow, (...args) => {
if (!isRemoved()) {
return handler(...args);
}
}));
if (!isFunction) {
handlerOrCallback.onRemove(wrapper.remove);
}
return wrapper;
};
/**
* {@link Callback} wraps user-supplied callbacks. Supports
* - removing a callback either when calling {@link remove} or if the user
* handler returns {@link Callback.REMOVE}
* - calling custom {@link onRemove} hooks
* - debouncing (via {@link wrap})
* - awaiting on an asynchronous handler and ensuring that the handler does not
* run concurrently to itself, i.e. subsequent {@link invoke}s will be queued
*
* @typeParam Args The type of arguments that the callback expects.
*/
exports.wrapCallback = wrapCallback;
class Callback {
/**
* @param handler The actual function to call. This should return one of
* the known {@link CallbackReturnType} values.
*/
constructor(handler) {
/**
* Call the handler with the given arguments.
*
* If the handler is asynchronous, it awaits on it. Furthermore, calls will
* always wait for previous calls to this handler to complete first, i.e. it
* never runs concurrently to itself. If you need multiple calls to the async
* handler to run concurrently, then wrap it in a non-async function that
* does not await it.
*
* The returned promise is rejected in two cases:
* - If the callback throws an error or returns a rejected Promise.
* - If the callback is removed _after_ you call {@link invoke} but before the
* handler is actually called (while it's waiting in the queue to be called)
* In this case, the rejection reason is {@link Callback.REMOVE}.
*
* @throws {@link Errors.LisnUsageError | LisnUsageError}
* If the callback is already removed.
*/
_defineProperty(this, "invoke", void 0);
/**
* Mark the callback as removed and call the registered {@link onRemove} hooks.
*
* Future attempts to call it will result in
* {@link Errors.LisnUsageError | LisnUsageError}.
*/
_defineProperty(this, "remove", void 0);
/**
* Returns true if the callback has been removed and cannot be called again.
*/
_defineProperty(this, "isRemoved", void 0);
/**
* Registers the given function to be called when the callback is removed.
*
* You can call {@link onRemove} multiple times to register multiple hooks.
*/
_defineProperty(this, "onRemove", void 0);
const logger = _debug.default ? new _debug.default.Logger({
name: "Callback",
logAtCreation: handler
}) : null;
let isRemoved = false;
const id = MC.SYMBOL();
const onRemove = MH.newSet();
this.isRemoved = () => isRemoved;
this.remove = () => {
if (!isRemoved) {
debug: logger === null || logger === void 0 || logger.debug8("Removing");
isRemoved = true;
for (const rmFn of onRemove) {
rmFn();
}
CallbackScheduler._clear(id);
}
};
this.onRemove = fn => onRemove.add(fn);
this.invoke = (...args) => MH.newPromise((resolve, reject) => {
debug: logger === null || logger === void 0 || logger.debug8("Calling with", args);
if (isRemoved) {
reject(MH.usageError("Callback has been removed"));
return;
}
CallbackScheduler._push(id, async () => {
let result;
try {
result = await handler(...args);
} catch (err) {
reject(err);
}
if (result === Callback.REMOVE) {
this.remove();
}
resolve();
}, reject);
});
callablesMap.set(this.invoke, this);
}
}
// ----------------------------------------
exports.Callback = Callback;
_Callback = Callback;
/**
* Possible return value for the handler.
*
* Do not do anything. Same as not retuning anything from the function.
*/
_defineProperty(Callback, "KEEP", MC.SYMBOL("KEEP"));
/**
* Possible return value for the handler.
*
* Will remove this callback.
*/
_defineProperty(Callback, "REMOVE", MC.SYMBOL("REMOVE"));
/**
* Wraps the given handler or callback as a callback, optionally debounced by
* the given debounce window.
*
* If the argument is already a callback _or an invoke method of a callback_,
* then the wrapper will call that callback and return the same value as it.
* It will also set up the returned wrapper callback so that it is removed
* when the original (given) callback is removed. However, removing the
* returned wrapper callback will _not_ cause the original callback (being
* wrapped) to be removed. If you want to do this, then do
* `wrapper.onRemove(wrapped.remove)`.
*
* Note that if the argument is a callback that's already debounced by a
* _larger_ window, then `debounceWindow` will have no effect.
*
* @param debounceWindow If non-0, the callback will be called at most
* every `debounceWindow` ms. The arguments it will
* be called with will be the last arguments the
* wrapper was called with.
*/
_defineProperty(Callback, "wrap", wrapCallback);
const callablesMap = MH.newWeakMap();
const CallbackScheduler = (() => {
const queues = MH.newMap();
const flush = async queue => {
// So that callbacks are always called asynchronously for consistency,
// await here before calling 1st
await null;
while (MH.lengthOf(queue)) {
// shouldn't throw anything as Callback must catch errors
queue[0]._running = true;
await queue[0]._task();
// only remove when done
queue.shift();
}
};
return {
_clear: id => {
const queue = queues.get(id);
if (queue) {
let item;
while (item = queue.shift()) {
if (!item._running) {
item._onRemove(Callback.REMOVE);
}
}
MH.deleteKey(queues, id);
}
},
_push: (id, task, onRemove) => {
let queue = queues.get(id);
if (!queue) {
queue = [];
queues.set(id, queue);
}
queue.push({
_task: task,
_onRemove: onRemove,
_running: false
});
if (MH.lengthOf(queue) === 1) {
flush(queue);
}
}
};
})();
//# sourceMappingURL=callback.cjs.map