@tldraw/utils 4.2.0-next.094f21ae35eb → 4.2.0-next.3634f876bff4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist-cjs/index.js
CHANGED
|
@@ -168,7 +168,7 @@ var import_version2 = require("./lib/version");
|
|
|
168
168
|
var import_warn = require("./lib/warn");
|
|
169
169
|
(0, import_version.registerTldrawLibraryVersion)(
|
|
170
170
|
"@tldraw/utils",
|
|
171
|
-
"4.2.0-next.
|
|
171
|
+
"4.2.0-next.3634f876bff4",
|
|
172
172
|
"cjs"
|
|
173
173
|
);
|
|
174
174
|
//# sourceMappingURL=index.js.map
|
package/dist-cjs/lib/throttle.js
CHANGED
|
@@ -25,21 +25,14 @@ module.exports = __toCommonJS(throttle_exports);
|
|
|
25
25
|
const isTest = () => typeof process !== "undefined" && process.env.NODE_ENV === "test" && // @ts-expect-error
|
|
26
26
|
!globalThis.__FORCE_RAF_IN_TESTS__;
|
|
27
27
|
const fpsQueue = [];
|
|
28
|
-
const targetFps =
|
|
29
|
-
const
|
|
30
|
-
const targetTimePerFrame = Math.floor(1e3 / targetFps) * timingVarianceFactor;
|
|
28
|
+
const targetFps = 60;
|
|
29
|
+
const targetTimePerFrame = Math.floor(1e3 / targetFps) * 0.9;
|
|
31
30
|
let frameRaf;
|
|
32
31
|
let flushRaf;
|
|
33
32
|
let lastFlushTime = -targetTimePerFrame;
|
|
34
|
-
const customFpsLastRunTime = /* @__PURE__ */ new WeakMap();
|
|
35
|
-
const customFpsGetters = /* @__PURE__ */ new WeakMap();
|
|
36
33
|
const flush = () => {
|
|
37
34
|
const queue = fpsQueue.splice(0, fpsQueue.length);
|
|
38
35
|
for (const fn of queue) {
|
|
39
|
-
const getTargetFps = customFpsGetters.get(fn);
|
|
40
|
-
if (getTargetFps) {
|
|
41
|
-
customFpsLastRunTime.set(fn, Date.now());
|
|
42
|
-
}
|
|
43
36
|
fn();
|
|
44
37
|
}
|
|
45
38
|
};
|
|
@@ -67,7 +60,7 @@ function tick(isOnNextFrame = false) {
|
|
|
67
60
|
});
|
|
68
61
|
}
|
|
69
62
|
}
|
|
70
|
-
function fpsThrottle(fn
|
|
63
|
+
function fpsThrottle(fn) {
|
|
71
64
|
if (isTest()) {
|
|
72
65
|
fn.cancel = () => {
|
|
73
66
|
if (frameRaf) {
|
|
@@ -81,18 +74,7 @@ function fpsThrottle(fn, getTargetFps) {
|
|
|
81
74
|
};
|
|
82
75
|
return fn;
|
|
83
76
|
}
|
|
84
|
-
if (getTargetFps) {
|
|
85
|
-
customFpsGetters.set(fn, getTargetFps);
|
|
86
|
-
}
|
|
87
77
|
const throttledFn = () => {
|
|
88
|
-
if (getTargetFps) {
|
|
89
|
-
const lastRun = customFpsLastRunTime.get(fn) ?? -Infinity;
|
|
90
|
-
const customTimePerFrame = Math.floor(1e3 / getTargetFps()) * timingVarianceFactor;
|
|
91
|
-
const elapsed = Date.now() - lastRun;
|
|
92
|
-
if (elapsed < customTimePerFrame) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
78
|
if (fpsQueue.includes(fn)) {
|
|
97
79
|
return;
|
|
98
80
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/throttle.ts"],
|
|
4
|
-
"sourcesContent": ["const isTest = () =>\n\ttypeof process !== 'undefined' &&\n\tprocess.env.NODE_ENV === 'test' &&\n\t// @ts-expect-error\n\t!globalThis.__FORCE_RAF_IN_TESTS__\n\nconst fpsQueue: Array<() => void> = []\nconst targetFps =
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAM,SAAS,MACd,OAAO,YAAY,eACnB,QAAQ,IAAI,aAAa;AAEzB,CAAC,WAAW;AAEb,MAAM,WAA8B,CAAC;AACrC,MAAM,YAAY;
|
|
4
|
+
"sourcesContent": ["const isTest = () =>\n\ttypeof process !== 'undefined' &&\n\tprocess.env.NODE_ENV === 'test' &&\n\t// @ts-expect-error\n\t!globalThis.__FORCE_RAF_IN_TESTS__\n\nconst fpsQueue: Array<() => void> = []\nconst targetFps = 60\nconst targetTimePerFrame = Math.floor(1000 / targetFps) * 0.9 // ~15ms - we allow for some variance as browsers aren't that precise.\nlet frameRaf: undefined | number\nlet flushRaf: undefined | number\nlet lastFlushTime = -targetTimePerFrame\n\nconst flush = () => {\n\tconst queue = fpsQueue.splice(0, fpsQueue.length)\n\tfor (const fn of queue) {\n\t\tfn()\n\t}\n}\n\nfunction tick(isOnNextFrame = false) {\n\tif (frameRaf) return\n\n\tconst now = Date.now()\n\tconst elapsed = now - lastFlushTime\n\n\tif (elapsed < targetTimePerFrame) {\n\t\t// If we're too early to flush, we need to wait until the next frame to try and flush again.\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tframeRaf = requestAnimationFrame(() => {\n\t\t\tframeRaf = undefined\n\t\t\ttick(true)\n\t\t})\n\t\treturn\n\t}\n\n\tif (isOnNextFrame) {\n\t\t// If we've already waited for the next frame to run the tick, then we can flush immediately\n\t\tif (flushRaf) return // ...though if there's a flush raf, that means we'll be flushing on this frame already, so we can do nothing here.\n\t\tlastFlushTime = now\n\t\tflush()\n\t} else {\n\t\t// If we haven't already waited for the next frame to run the tick, we need to wait until the next frame to flush.\n\t\tif (flushRaf) return // ...though if there's a flush raf, that means we'll be flushing on the next frame already, so we can do nothing here.\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tflushRaf = requestAnimationFrame(() => {\n\t\t\tflushRaf = undefined\n\t\t\tlastFlushTime = now\n\t\t\tflush()\n\t\t})\n\t}\n}\n\n/**\n * Creates a throttled version of a function that executes at most once per frame (60fps).\n * Subsequent calls within the same frame are ignored, ensuring smooth performance\n * for high-frequency events like mouse movements or scroll events.\n *\n * @param fn - The function to throttle, optionally with a cancel method\n * @returns A throttled function with an optional cancel method to remove pending calls\n *\n * @example\n * ```ts\n * const updateCanvas = fpsThrottle(() => {\n * // This will run at most once per frame (~16.67ms)\n * redrawCanvas()\n * })\n *\n * // Call as often as you want - automatically throttled to 60fps\n * document.addEventListener('mousemove', updateCanvas)\n *\n * // Cancel pending calls if needed\n * updateCanvas.cancel?.()\n * ```\n *\n * @internal\n */\nexport function fpsThrottle(fn: { (): void; cancel?(): void }): {\n\t(): void\n\tcancel?(): void\n} {\n\tif (isTest()) {\n\t\tfn.cancel = () => {\n\t\t\tif (frameRaf) {\n\t\t\t\tcancelAnimationFrame(frameRaf)\n\t\t\t\tframeRaf = undefined\n\t\t\t}\n\t\t\tif (flushRaf) {\n\t\t\t\tcancelAnimationFrame(flushRaf)\n\t\t\t\tflushRaf = undefined\n\t\t\t}\n\t\t}\n\t\treturn fn\n\t}\n\n\tconst throttledFn = () => {\n\t\tif (fpsQueue.includes(fn)) {\n\t\t\treturn\n\t\t}\n\t\tfpsQueue.push(fn)\n\t\ttick()\n\t}\n\tthrottledFn.cancel = () => {\n\t\tconst index = fpsQueue.indexOf(fn)\n\t\tif (index > -1) {\n\t\t\tfpsQueue.splice(index, 1)\n\t\t}\n\t}\n\treturn throttledFn\n}\n\n/**\n * Schedules a function to execute on the next animation frame, targeting 60fps.\n * If the same function is passed multiple times before the frame executes,\n * it will only be called once, effectively batching multiple calls.\n *\n * @param fn - The function to execute on the next frame\n * @returns A cancel function that can prevent execution if called before the next frame\n *\n * @example\n * ```ts\n * const updateUI = throttleToNextFrame(() => {\n * // Batches multiple calls into the next animation frame\n * updateStatusBar()\n * refreshToolbar()\n * })\n *\n * // Multiple calls within the same frame are batched\n * updateUI() // Will execute\n * updateUI() // Ignored (same function already queued)\n * updateUI() // Ignored (same function already queued)\n *\n * // Get cancel function to prevent execution\n * const cancel = updateUI()\n * cancel() // Prevents execution if called before next frame\n * ```\n *\n * @internal\n */\nexport function throttleToNextFrame(fn: () => void): () => void {\n\tif (isTest()) {\n\t\tfn()\n\t\treturn () => void null // noop\n\t}\n\n\tif (!fpsQueue.includes(fn)) {\n\t\tfpsQueue.push(fn)\n\t\ttick()\n\t}\n\n\treturn () => {\n\t\tconst index = fpsQueue.indexOf(fn)\n\t\tif (index > -1) {\n\t\t\tfpsQueue.splice(index, 1)\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAAM,SAAS,MACd,OAAO,YAAY,eACnB,QAAQ,IAAI,aAAa;AAEzB,CAAC,WAAW;AAEb,MAAM,WAA8B,CAAC;AACrC,MAAM,YAAY;AAClB,MAAM,qBAAqB,KAAK,MAAM,MAAO,SAAS,IAAI;AAC1D,IAAI;AACJ,IAAI;AACJ,IAAI,gBAAgB,CAAC;AAErB,MAAM,QAAQ,MAAM;AACnB,QAAM,QAAQ,SAAS,OAAO,GAAG,SAAS,MAAM;AAChD,aAAW,MAAM,OAAO;AACvB,OAAG;AAAA,EACJ;AACD;AAEA,SAAS,KAAK,gBAAgB,OAAO;AACpC,MAAI,SAAU;AAEd,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,UAAU,MAAM;AAEtB,MAAI,UAAU,oBAAoB;AAGjC,eAAW,sBAAsB,MAAM;AACtC,iBAAW;AACX,WAAK,IAAI;AAAA,IACV,CAAC;AACD;AAAA,EACD;AAEA,MAAI,eAAe;AAElB,QAAI,SAAU;AACd,oBAAgB;AAChB,UAAM;AAAA,EACP,OAAO;AAEN,QAAI,SAAU;AAEd,eAAW,sBAAsB,MAAM;AACtC,iBAAW;AACX,sBAAgB;AAChB,YAAM;AAAA,IACP,CAAC;AAAA,EACF;AACD;AA0BO,SAAS,YAAY,IAG1B;AACD,MAAI,OAAO,GAAG;AACb,OAAG,SAAS,MAAM;AACjB,UAAI,UAAU;AACb,6BAAqB,QAAQ;AAC7B,mBAAW;AAAA,MACZ;AACA,UAAI,UAAU;AACb,6BAAqB,QAAQ;AAC7B,mBAAW;AAAA,MACZ;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAEA,QAAM,cAAc,MAAM;AACzB,QAAI,SAAS,SAAS,EAAE,GAAG;AAC1B;AAAA,IACD;AACA,aAAS,KAAK,EAAE;AAChB,SAAK;AAAA,EACN;AACA,cAAY,SAAS,MAAM;AAC1B,UAAM,QAAQ,SAAS,QAAQ,EAAE;AACjC,QAAI,QAAQ,IAAI;AACf,eAAS,OAAO,OAAO,CAAC;AAAA,IACzB;AAAA,EACD;AACA,SAAO;AACR;AA8BO,SAAS,oBAAoB,IAA4B;AAC/D,MAAI,OAAO,GAAG;AACb,OAAG;AACH,WAAO,MAAM;AAAA,EACd;AAEA,MAAI,CAAC,SAAS,SAAS,EAAE,GAAG;AAC3B,aAAS,KAAK,EAAE;AAChB,SAAK;AAAA,EACN;AAEA,SAAO,MAAM;AACZ,UAAM,QAAQ,SAAS,QAAQ,EAAE;AACjC,QAAI,QAAQ,IAAI;AACf,eAAS,OAAO,OAAO,CAAC;AAAA,IACzB;AAAA,EACD;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.mjs
CHANGED
|
@@ -101,7 +101,7 @@ import { registerTldrawLibraryVersion as registerTldrawLibraryVersion2 } from ".
|
|
|
101
101
|
import { warnDeprecatedGetter, warnOnce } from "./lib/warn.mjs";
|
|
102
102
|
registerTldrawLibraryVersion(
|
|
103
103
|
"@tldraw/utils",
|
|
104
|
-
"4.2.0-next.
|
|
104
|
+
"4.2.0-next.3634f876bff4",
|
|
105
105
|
"esm"
|
|
106
106
|
);
|
|
107
107
|
export {
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
const isTest = () => typeof process !== "undefined" && process.env.NODE_ENV === "test" && // @ts-expect-error
|
|
2
2
|
!globalThis.__FORCE_RAF_IN_TESTS__;
|
|
3
3
|
const fpsQueue = [];
|
|
4
|
-
const targetFps =
|
|
5
|
-
const
|
|
6
|
-
const targetTimePerFrame = Math.floor(1e3 / targetFps) * timingVarianceFactor;
|
|
4
|
+
const targetFps = 60;
|
|
5
|
+
const targetTimePerFrame = Math.floor(1e3 / targetFps) * 0.9;
|
|
7
6
|
let frameRaf;
|
|
8
7
|
let flushRaf;
|
|
9
8
|
let lastFlushTime = -targetTimePerFrame;
|
|
10
|
-
const customFpsLastRunTime = /* @__PURE__ */ new WeakMap();
|
|
11
|
-
const customFpsGetters = /* @__PURE__ */ new WeakMap();
|
|
12
9
|
const flush = () => {
|
|
13
10
|
const queue = fpsQueue.splice(0, fpsQueue.length);
|
|
14
11
|
for (const fn of queue) {
|
|
15
|
-
const getTargetFps = customFpsGetters.get(fn);
|
|
16
|
-
if (getTargetFps) {
|
|
17
|
-
customFpsLastRunTime.set(fn, Date.now());
|
|
18
|
-
}
|
|
19
12
|
fn();
|
|
20
13
|
}
|
|
21
14
|
};
|
|
@@ -43,7 +36,7 @@ function tick(isOnNextFrame = false) {
|
|
|
43
36
|
});
|
|
44
37
|
}
|
|
45
38
|
}
|
|
46
|
-
function fpsThrottle(fn
|
|
39
|
+
function fpsThrottle(fn) {
|
|
47
40
|
if (isTest()) {
|
|
48
41
|
fn.cancel = () => {
|
|
49
42
|
if (frameRaf) {
|
|
@@ -57,18 +50,7 @@ function fpsThrottle(fn, getTargetFps) {
|
|
|
57
50
|
};
|
|
58
51
|
return fn;
|
|
59
52
|
}
|
|
60
|
-
if (getTargetFps) {
|
|
61
|
-
customFpsGetters.set(fn, getTargetFps);
|
|
62
|
-
}
|
|
63
53
|
const throttledFn = () => {
|
|
64
|
-
if (getTargetFps) {
|
|
65
|
-
const lastRun = customFpsLastRunTime.get(fn) ?? -Infinity;
|
|
66
|
-
const customTimePerFrame = Math.floor(1e3 / getTargetFps()) * timingVarianceFactor;
|
|
67
|
-
const elapsed = Date.now() - lastRun;
|
|
68
|
-
if (elapsed < customTimePerFrame) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
54
|
if (fpsQueue.includes(fn)) {
|
|
73
55
|
return;
|
|
74
56
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/throttle.ts"],
|
|
4
|
-
"sourcesContent": ["const isTest = () =>\n\ttypeof process !== 'undefined' &&\n\tprocess.env.NODE_ENV === 'test' &&\n\t// @ts-expect-error\n\t!globalThis.__FORCE_RAF_IN_TESTS__\n\nconst fpsQueue: Array<() => void> = []\nconst targetFps =
|
|
5
|
-
"mappings": "AAAA,MAAM,SAAS,MACd,OAAO,YAAY,eACnB,QAAQ,IAAI,aAAa;AAEzB,CAAC,WAAW;AAEb,MAAM,WAA8B,CAAC;AACrC,MAAM,YAAY;
|
|
4
|
+
"sourcesContent": ["const isTest = () =>\n\ttypeof process !== 'undefined' &&\n\tprocess.env.NODE_ENV === 'test' &&\n\t// @ts-expect-error\n\t!globalThis.__FORCE_RAF_IN_TESTS__\n\nconst fpsQueue: Array<() => void> = []\nconst targetFps = 60\nconst targetTimePerFrame = Math.floor(1000 / targetFps) * 0.9 // ~15ms - we allow for some variance as browsers aren't that precise.\nlet frameRaf: undefined | number\nlet flushRaf: undefined | number\nlet lastFlushTime = -targetTimePerFrame\n\nconst flush = () => {\n\tconst queue = fpsQueue.splice(0, fpsQueue.length)\n\tfor (const fn of queue) {\n\t\tfn()\n\t}\n}\n\nfunction tick(isOnNextFrame = false) {\n\tif (frameRaf) return\n\n\tconst now = Date.now()\n\tconst elapsed = now - lastFlushTime\n\n\tif (elapsed < targetTimePerFrame) {\n\t\t// If we're too early to flush, we need to wait until the next frame to try and flush again.\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tframeRaf = requestAnimationFrame(() => {\n\t\t\tframeRaf = undefined\n\t\t\ttick(true)\n\t\t})\n\t\treturn\n\t}\n\n\tif (isOnNextFrame) {\n\t\t// If we've already waited for the next frame to run the tick, then we can flush immediately\n\t\tif (flushRaf) return // ...though if there's a flush raf, that means we'll be flushing on this frame already, so we can do nothing here.\n\t\tlastFlushTime = now\n\t\tflush()\n\t} else {\n\t\t// If we haven't already waited for the next frame to run the tick, we need to wait until the next frame to flush.\n\t\tif (flushRaf) return // ...though if there's a flush raf, that means we'll be flushing on the next frame already, so we can do nothing here.\n\t\t// eslint-disable-next-line no-restricted-globals\n\t\tflushRaf = requestAnimationFrame(() => {\n\t\t\tflushRaf = undefined\n\t\t\tlastFlushTime = now\n\t\t\tflush()\n\t\t})\n\t}\n}\n\n/**\n * Creates a throttled version of a function that executes at most once per frame (60fps).\n * Subsequent calls within the same frame are ignored, ensuring smooth performance\n * for high-frequency events like mouse movements or scroll events.\n *\n * @param fn - The function to throttle, optionally with a cancel method\n * @returns A throttled function with an optional cancel method to remove pending calls\n *\n * @example\n * ```ts\n * const updateCanvas = fpsThrottle(() => {\n * // This will run at most once per frame (~16.67ms)\n * redrawCanvas()\n * })\n *\n * // Call as often as you want - automatically throttled to 60fps\n * document.addEventListener('mousemove', updateCanvas)\n *\n * // Cancel pending calls if needed\n * updateCanvas.cancel?.()\n * ```\n *\n * @internal\n */\nexport function fpsThrottle(fn: { (): void; cancel?(): void }): {\n\t(): void\n\tcancel?(): void\n} {\n\tif (isTest()) {\n\t\tfn.cancel = () => {\n\t\t\tif (frameRaf) {\n\t\t\t\tcancelAnimationFrame(frameRaf)\n\t\t\t\tframeRaf = undefined\n\t\t\t}\n\t\t\tif (flushRaf) {\n\t\t\t\tcancelAnimationFrame(flushRaf)\n\t\t\t\tflushRaf = undefined\n\t\t\t}\n\t\t}\n\t\treturn fn\n\t}\n\n\tconst throttledFn = () => {\n\t\tif (fpsQueue.includes(fn)) {\n\t\t\treturn\n\t\t}\n\t\tfpsQueue.push(fn)\n\t\ttick()\n\t}\n\tthrottledFn.cancel = () => {\n\t\tconst index = fpsQueue.indexOf(fn)\n\t\tif (index > -1) {\n\t\t\tfpsQueue.splice(index, 1)\n\t\t}\n\t}\n\treturn throttledFn\n}\n\n/**\n * Schedules a function to execute on the next animation frame, targeting 60fps.\n * If the same function is passed multiple times before the frame executes,\n * it will only be called once, effectively batching multiple calls.\n *\n * @param fn - The function to execute on the next frame\n * @returns A cancel function that can prevent execution if called before the next frame\n *\n * @example\n * ```ts\n * const updateUI = throttleToNextFrame(() => {\n * // Batches multiple calls into the next animation frame\n * updateStatusBar()\n * refreshToolbar()\n * })\n *\n * // Multiple calls within the same frame are batched\n * updateUI() // Will execute\n * updateUI() // Ignored (same function already queued)\n * updateUI() // Ignored (same function already queued)\n *\n * // Get cancel function to prevent execution\n * const cancel = updateUI()\n * cancel() // Prevents execution if called before next frame\n * ```\n *\n * @internal\n */\nexport function throttleToNextFrame(fn: () => void): () => void {\n\tif (isTest()) {\n\t\tfn()\n\t\treturn () => void null // noop\n\t}\n\n\tif (!fpsQueue.includes(fn)) {\n\t\tfpsQueue.push(fn)\n\t\ttick()\n\t}\n\n\treturn () => {\n\t\tconst index = fpsQueue.indexOf(fn)\n\t\tif (index > -1) {\n\t\t\tfpsQueue.splice(index, 1)\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "AAAA,MAAM,SAAS,MACd,OAAO,YAAY,eACnB,QAAQ,IAAI,aAAa;AAEzB,CAAC,WAAW;AAEb,MAAM,WAA8B,CAAC;AACrC,MAAM,YAAY;AAClB,MAAM,qBAAqB,KAAK,MAAM,MAAO,SAAS,IAAI;AAC1D,IAAI;AACJ,IAAI;AACJ,IAAI,gBAAgB,CAAC;AAErB,MAAM,QAAQ,MAAM;AACnB,QAAM,QAAQ,SAAS,OAAO,GAAG,SAAS,MAAM;AAChD,aAAW,MAAM,OAAO;AACvB,OAAG;AAAA,EACJ;AACD;AAEA,SAAS,KAAK,gBAAgB,OAAO;AACpC,MAAI,SAAU;AAEd,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,UAAU,MAAM;AAEtB,MAAI,UAAU,oBAAoB;AAGjC,eAAW,sBAAsB,MAAM;AACtC,iBAAW;AACX,WAAK,IAAI;AAAA,IACV,CAAC;AACD;AAAA,EACD;AAEA,MAAI,eAAe;AAElB,QAAI,SAAU;AACd,oBAAgB;AAChB,UAAM;AAAA,EACP,OAAO;AAEN,QAAI,SAAU;AAEd,eAAW,sBAAsB,MAAM;AACtC,iBAAW;AACX,sBAAgB;AAChB,YAAM;AAAA,IACP,CAAC;AAAA,EACF;AACD;AA0BO,SAAS,YAAY,IAG1B;AACD,MAAI,OAAO,GAAG;AACb,OAAG,SAAS,MAAM;AACjB,UAAI,UAAU;AACb,6BAAqB,QAAQ;AAC7B,mBAAW;AAAA,MACZ;AACA,UAAI,UAAU;AACb,6BAAqB,QAAQ;AAC7B,mBAAW;AAAA,MACZ;AAAA,IACD;AACA,WAAO;AAAA,EACR;AAEA,QAAM,cAAc,MAAM;AACzB,QAAI,SAAS,SAAS,EAAE,GAAG;AAC1B;AAAA,IACD;AACA,aAAS,KAAK,EAAE;AAChB,SAAK;AAAA,EACN;AACA,cAAY,SAAS,MAAM;AAC1B,UAAM,QAAQ,SAAS,QAAQ,EAAE;AACjC,QAAI,QAAQ,IAAI;AACf,eAAS,OAAO,OAAO,CAAC;AAAA,IACzB;AAAA,EACD;AACA,SAAO;AACR;AA8BO,SAAS,oBAAoB,IAA4B;AAC/D,MAAI,OAAO,GAAG;AACb,OAAG;AACH,WAAO,MAAM;AAAA,EACd;AAEA,MAAI,CAAC,SAAS,SAAS,EAAE,GAAG;AAC3B,aAAS,KAAK,EAAE;AAChB,SAAK;AAAA,EACN;AAEA,SAAO,MAAM;AACZ,UAAM,QAAQ,SAAS,QAAQ,EAAE;AACjC,QAAI,QAAQ,IAAI;AACf,eAAS,OAAO,OAAO,CAAC;AAAA,IACzB;AAAA,EACD;AACD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
package/src/lib/throttle.ts
CHANGED
|
@@ -5,28 +5,15 @@ const isTest = () =>
|
|
|
5
5
|
!globalThis.__FORCE_RAF_IN_TESTS__
|
|
6
6
|
|
|
7
7
|
const fpsQueue: Array<() => void> = []
|
|
8
|
-
const targetFps =
|
|
9
|
-
|
|
10
|
-
// by aiming slightly below the theoretical frame duration (e.g., ~7.5ms instead of 8.33ms for 120fps)
|
|
11
|
-
const timingVarianceFactor = 0.9
|
|
12
|
-
const targetTimePerFrame = Math.floor(1000 / targetFps) * timingVarianceFactor // ~7ms
|
|
8
|
+
const targetFps = 60
|
|
9
|
+
const targetTimePerFrame = Math.floor(1000 / targetFps) * 0.9 // ~15ms - we allow for some variance as browsers aren't that precise.
|
|
13
10
|
let frameRaf: undefined | number
|
|
14
11
|
let flushRaf: undefined | number
|
|
15
12
|
let lastFlushTime = -targetTimePerFrame
|
|
16
13
|
|
|
17
|
-
// Track custom FPS timing per function
|
|
18
|
-
const customFpsLastRunTime = new WeakMap<() => void, number>()
|
|
19
|
-
// Map function to its custom FPS getter
|
|
20
|
-
const customFpsGetters = new WeakMap<() => void, () => number>()
|
|
21
|
-
|
|
22
14
|
const flush = () => {
|
|
23
15
|
const queue = fpsQueue.splice(0, fpsQueue.length)
|
|
24
16
|
for (const fn of queue) {
|
|
25
|
-
// If this function has custom FPS, update timestamp when executing
|
|
26
|
-
const getTargetFps = customFpsGetters.get(fn)
|
|
27
|
-
if (getTargetFps) {
|
|
28
|
-
customFpsLastRunTime.set(fn, Date.now())
|
|
29
|
-
}
|
|
30
17
|
fn()
|
|
31
18
|
}
|
|
32
19
|
}
|
|
@@ -65,41 +52,30 @@ function tick(isOnNextFrame = false) {
|
|
|
65
52
|
}
|
|
66
53
|
|
|
67
54
|
/**
|
|
68
|
-
* Creates a throttled version of a function that executes at most once per frame.
|
|
69
|
-
* The default target frame rate is 120fps, but can be customized per function.
|
|
55
|
+
* Creates a throttled version of a function that executes at most once per frame (60fps).
|
|
70
56
|
* Subsequent calls within the same frame are ignored, ensuring smooth performance
|
|
71
57
|
* for high-frequency events like mouse movements or scroll events.
|
|
72
58
|
*
|
|
73
59
|
* @param fn - The function to throttle, optionally with a cancel method
|
|
74
|
-
* @param getTargetFps - Optional function that returns the current target FPS rate for custom throttling
|
|
75
60
|
* @returns A throttled function with an optional cancel method to remove pending calls
|
|
76
61
|
*
|
|
77
62
|
* @example
|
|
78
63
|
* ```ts
|
|
79
|
-
* // Default 120fps throttling
|
|
80
64
|
* const updateCanvas = fpsThrottle(() => {
|
|
81
|
-
* // This will run at most once per frame (~
|
|
65
|
+
* // This will run at most once per frame (~16.67ms)
|
|
82
66
|
* redrawCanvas()
|
|
83
67
|
* })
|
|
84
68
|
*
|
|
85
|
-
* // Call as often as you want - automatically throttled to
|
|
69
|
+
* // Call as often as you want - automatically throttled to 60fps
|
|
86
70
|
* document.addEventListener('mousemove', updateCanvas)
|
|
87
71
|
*
|
|
88
72
|
* // Cancel pending calls if needed
|
|
89
73
|
* updateCanvas.cancel?.()
|
|
90
|
-
*
|
|
91
|
-
* // Custom FPS throttling for less critical updates
|
|
92
|
-
* const slowUpdate = fpsThrottle(() => {
|
|
93
|
-
* heavyComputation()
|
|
94
|
-
* }, () => 30) // Throttle to 30fps
|
|
95
74
|
* ```
|
|
96
75
|
*
|
|
97
76
|
* @internal
|
|
98
77
|
*/
|
|
99
|
-
export function fpsThrottle(
|
|
100
|
-
fn: { (): void; cancel?(): void },
|
|
101
|
-
getTargetFps?: () => number
|
|
102
|
-
): {
|
|
78
|
+
export function fpsThrottle(fn: { (): void; cancel?(): void }): {
|
|
103
79
|
(): void
|
|
104
80
|
cancel?(): void
|
|
105
81
|
} {
|
|
@@ -117,23 +93,7 @@ export function fpsThrottle(
|
|
|
117
93
|
return fn
|
|
118
94
|
}
|
|
119
95
|
|
|
120
|
-
// Store custom FPS getter if provided
|
|
121
|
-
if (getTargetFps) {
|
|
122
|
-
customFpsGetters.set(fn, getTargetFps)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
96
|
const throttledFn = () => {
|
|
126
|
-
// Custom FPS - check timing before queuing
|
|
127
|
-
if (getTargetFps) {
|
|
128
|
-
const lastRun = customFpsLastRunTime.get(fn) ?? -Infinity
|
|
129
|
-
const customTimePerFrame = Math.floor(1000 / getTargetFps()) * timingVarianceFactor
|
|
130
|
-
const elapsed = Date.now() - lastRun
|
|
131
|
-
|
|
132
|
-
if (elapsed < customTimePerFrame) {
|
|
133
|
-
// Not ready yet, don't queue
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
97
|
if (fpsQueue.includes(fn)) {
|
|
138
98
|
return
|
|
139
99
|
}
|
|
@@ -150,7 +110,7 @@ export function fpsThrottle(
|
|
|
150
110
|
}
|
|
151
111
|
|
|
152
112
|
/**
|
|
153
|
-
* Schedules a function to execute on the next animation frame, targeting
|
|
113
|
+
* Schedules a function to execute on the next animation frame, targeting 60fps.
|
|
154
114
|
* If the same function is passed multiple times before the frame executes,
|
|
155
115
|
* it will only be called once, effectively batching multiple calls.
|
|
156
116
|
*
|