@rx-angular/cdk
Version:
@rx-angular/cdk is a Component Development Kit for ergonomic and highly performant angular applications. It helps to to build Large scale applications, UI libs, state management, rendering systems and much more. Furthermore the unique way of mixing reacti
470 lines (465 loc) • 16.4 kB
JavaScript
import { ɵglobal as _global } from '@angular/core';
function push(heap, node) {
const index = heap.length;
heap.push(node);
siftUp(heap, node, index);
}
function peek(heap) {
const first = heap[0];
return first === undefined ? null : first;
}
function pop(heap) {
const first = heap[0];
if (first !== undefined) {
const last = heap.pop();
if (last !== first) {
heap[0] = last;
siftDown(heap, last, 0);
}
return first;
}
else {
return null;
}
}
function siftUp(heap, node, i) {
let index = i;
// eslint-disable-next-line no-constant-condition
while (true) {
const parentIndex = (index - 1) >>> 1;
const parent = heap[parentIndex];
if (parent !== undefined && compare(parent, node) > 0) {
// The parent is larger. Swap positions.
heap[parentIndex] = node;
heap[index] = parent;
index = parentIndex;
}
else {
// The parent is smaller. Exit.
return;
}
}
}
function siftDown(heap, node, i) {
let index = i;
const length = heap.length;
while (index < length) {
const leftIndex = (index + 1) * 2 - 1;
const left = heap[leftIndex];
const rightIndex = leftIndex + 1;
const right = heap[rightIndex];
// If the left or right node is smaller, swap with the smaller of those.
if (left !== undefined && compare(left, node) < 0) {
if (right !== undefined && compare(right, left) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
}
else {
heap[index] = left;
heap[leftIndex] = node;
index = leftIndex;
}
}
else if (right !== undefined && compare(right, node) < 0) {
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
}
else {
// Neither child is smaller. Exit.
return;
}
}
}
function compare(a, b) {
// Compare sort index first, then task id.
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}
// see https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js
let getCurrentTime;
const hasPerformanceNow = typeof _global.performance === 'object' &&
typeof _global.performance.now === 'function';
if (hasPerformanceNow) {
const localPerformance = _global.performance;
getCurrentTime = () => localPerformance.now();
}
else {
const localDate = Date;
const initialTime = localDate.now();
getCurrentTime = () => localDate.now() - initialTime;
}
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
const maxSigned31BitInt = 1073741823;
// Times out immediately
const IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
const USER_BLOCKING_PRIORITY_TIMEOUT = 250;
const NORMAL_PRIORITY_TIMEOUT = 5000;
const LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
// Tasks are stored on a min heap
const taskQueue = [];
const timerQueue = [];
// Incrementing id counter. Used to maintain insertion order.
let taskIdCounter = 1;
let currentTask = null;
let currentPriorityLevel = 3 /* PriorityLevel.NormalPriority */;
// This is set while performing work, to prevent re-entrancy.
let isPerformingWork = false;
let isHostCallbackScheduled = false;
let isHostTimeoutScheduled = false;
// Capture local references to native APIs, in case a polyfill overrides them.
const setTimeout = _global.setTimeout;
const clearTimeout = _global.clearTimeout;
const setImmediate = _global.setImmediate; // IE and Node.js + jsdom
const messageChannel = _global.MessageChannel;
const defaultZone = {
run: (fn) => fn(),
};
function advanceTimers(currentTime) {
// Check for tasks that are no longer delayed and add them to the queue.
let timer = peek(timerQueue);
while (timer !== null) {
if (timer.callback === null) {
// Timer was cancelled.
pop(timerQueue);
}
else if (timer.startTime <= currentTime) {
// Timer fired. Transfer to the task queue.
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
}
else {
// Remaining timers are pending.
return;
}
timer = peek(timerQueue);
}
}
function handleTimeout(currentTime) {
isHostTimeoutScheduled = false;
advanceTimers(currentTime);
if (!isHostCallbackScheduled) {
if (peek(taskQueue) !== null) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
}
}
}
function flushWork(hasTimeRemaining, initialTime) {
// We'll need a host callback the next time work is scheduled.
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
// We scheduled a timeout but it's no longer needed. Cancel it.
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
isPerformingWork = true;
const previousPriorityLevel = currentPriorityLevel;
try {
return workLoop(hasTimeRemaining, initialTime);
}
finally {
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
}
}
function workLoop(hasTimeRemaining, initialTime, _currentTask) {
let currentTime = initialTime;
if (_currentTask) {
currentTask = _currentTask;
}
else {
advanceTimers(currentTime);
currentTask = peek(taskQueue);
}
let zoneChanged = false;
const hitDeadline = () => currentTask &&
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost());
if (!hitDeadline()) {
const ngZone = currentTask.ngZone || defaultZone;
ngZone.run(() => {
while (currentTask !== null && !zoneChanged) {
if (hitDeadline()) {
break;
}
const callback = currentTask.callback;
if (typeof callback === 'function') {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === 'function') {
currentTask.callback = continuationCallback;
}
else {
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
}
else {
pop(taskQueue);
}
currentTask = peek(taskQueue);
zoneChanged =
currentTask?.ngZone != null && currentTask.ngZone !== ngZone;
}
});
}
// we need to check if leaving `NgZone` (tick => detectChanges) caused other
// directives to add tasks to the queue. If there is one and we still didn't
// hit the deadline, run the workLoop again in order to flush everything thats
// left.
// Otherwise, newly added tasks won't run as `performingWork` is still `true`
currentTask = currentTask ?? peek(taskQueue);
// We should also re-calculate the currentTime, as we need to account for the execution
// time of the NgZone tasks as well.
// If there is still a task in the queue, but no time is left for executing it,
// the scheduler will re-schedule the next tick anyway
currentTime = getCurrentTime();
if (zoneChanged || (currentTask && !hitDeadline())) {
return workLoop(hasTimeRemaining, currentTime, currentTask);
}
// Return whether there's additional work
if (currentTask !== null) {
return true;
}
else {
const firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}
function scheduleCallback(priorityLevel, callback, options) {
const currentTime = getCurrentTime();
let startTime;
if (typeof options === 'object' && options !== null) {
const delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
startTime = currentTime + delay;
}
else {
startTime = currentTime;
}
}
else {
startTime = currentTime;
}
let timeout;
switch (priorityLevel) {
case 1 /* PriorityLevel.ImmediatePriority */:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case 2 /* PriorityLevel.UserBlockingPriority */:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case 5 /* PriorityLevel.IdlePriority */:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case 4 /* PriorityLevel.LowPriority */:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case 3 /* PriorityLevel.NormalPriority */:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
const expirationTime = startTime + timeout;
const newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
ngZone: options?.ngZone || null,
};
if (startTime > currentTime) {
// This is a delayed task.
newTask.sortIndex = startTime;
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// All tasks are delayed, and this is the task with the earliest delay.
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout();
}
else {
isHostTimeoutScheduled = true;
}
// Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime);
}
}
else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
// Schedule a host callback, if needed. If we're already performing work,
// wait until the next time we yield.
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}
function cancelCallback(task) {
// Null out the callback to indicate the task has been canceled. (Can't
// remove from the queue because you can't remove arbitrary nodes from an
// array based heap, only the first one.)
task.callback = null;
}
let isMessageLoopRunning = false;
let scheduledHostCallback = null;
let taskTimeoutID = -1;
// Scheduler periodically yields in case there is other work on the main
// thread, like user events. By default, it yields multiple times per frame.
// It does not attempt to align with frame boundaries, since most tasks don't
// need to be frame aligned; for those that do, use requestAnimationFrame.
let yieldInterval = 16;
let needsPaint = false;
let queueStartTime = -1;
function shouldYieldToHost() {
if (needsPaint) {
// There's a pending paint (signaled by `requestPaint`). Yield now.
return true;
}
const timeElapsed = getCurrentTime() - queueStartTime;
if (timeElapsed < yieldInterval) {
// The main thread has only been blocked for a really short amount of time;
// smaller than a single frame. Don't yield yet.
return false;
}
// `isInputPending` isn't available. Yield now.
return true;
}
function requestPaint() {
needsPaint = true;
}
function forceFrameRate(fps) {
if (fps < 0 || fps > 125) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
console.error('forceFrameRate takes a positive int between 0 and 125, ' +
'forcing frame rates higher than 125 fps is not supported');
}
return;
}
if (fps > 0) {
yieldInterval = Math.floor(1000 / fps);
}
else {
// reset the framerate
yieldInterval = 5;
}
// be aware of browser housekeeping work (~6ms per frame)
// according to https://developers.google.com/web/fundamentals/performance/rendering
yieldInterval = Math.max(5, yieldInterval - 6);
}
const performWorkUntilDeadline = () => {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// Yield after `yieldInterval` ms, regardless of where we are in the vsync
// cycle. This means there's always time remaining at the beginning of
// the message event.
queueStartTime = currentTime;
const hasTimeRemaining = true;
// If a scheduler task throws, exit the current browser task so the
// error can be observed.
//
// Intentionally not using a try-catch, since that makes some debugging
// techniques harder. Instead, if `scheduledHostCallback` errors, then
// `hasMoreWork` will remain true, and we'll continue the work loop.
let hasMoreWork = true;
try {
hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
}
finally {
if (hasMoreWork) {
// If there's more work, schedule the next message event at the end
// of the preceding one.
schedulePerformWorkUntilDeadline();
}
else {
isMessageLoopRunning = false;
scheduledHostCallback = null;
}
}
}
else {
isMessageLoopRunning = false;
}
// Yielding to the browser will give it a chance to paint, so we can
// reset this.
needsPaint = false;
};
let schedulePerformWorkUntilDeadline;
if (typeof setImmediate === 'function') {
// Node.js and old IE.
// There's a few reasons for why we prefer setImmediate.
//
// Unlike MessageChannel, it doesn't prevent a Node.js process from exiting.
// (Even though this is a DOM fork of the Scheduler, you could get here
// with a mix of Node.js 15+, which has a MessageChannel, and jsdom.)
// https://github.com/facebook/react/issues/20756
//
// But also, it runs earlier which is the semantic we want.
// If other browsers ever implement it, it's better to use it.
// Although both of these would be inferior to native scheduling.
schedulePerformWorkUntilDeadline = () => {
setImmediate(performWorkUntilDeadline);
};
}
else if (typeof messageChannel !== 'undefined') {
const channel = new messageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = () => {
port.postMessage(null);
};
}
else {
// We should only fallback here in non-browser environments.
schedulePerformWorkUntilDeadline = () => {
setTimeout(performWorkUntilDeadline, 0);
};
}
function requestHostCallback(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
schedulePerformWorkUntilDeadline();
}
}
function requestHostTimeout(callback, ms) {
taskTimeoutID = setTimeout(() => {
callback(getCurrentTime());
}, ms);
}
function cancelHostTimeout() {
clearTimeout(taskTimeoutID);
taskTimeoutID = -1;
}
/**
* Generated bundle index. Do not edit.
*/
export { cancelCallback, forceFrameRate, scheduleCallback };
//# sourceMappingURL=cdk-internals-scheduler.mjs.map