react-jitter 0.3.0 → 0.4.0

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/README.md CHANGED
@@ -128,6 +128,44 @@ window.reactJitter.onRender = (render) => {
128
128
 
129
129
  Modern bundlers will tree-shake the `import` and the function call from your production build, so it will have zero performance impact.
130
130
 
131
+ ### Advanced: Custom Comparator Selection
132
+
133
+ By default, React Jitter uses the `deepEqual` comparator to detect changes in hook values. However, you can customize which comparator is used on a per-hook basis using the `selectComparator` function. This is useful when dealing with circular data structures or when you need different comparison strategies for different hooks.
134
+
135
+ ```js
136
+ // Set a custom comparator selector
137
+ window.reactJitter.selectComparator = (hookAddress) => {
138
+ // Use circularDeepEqual for hooks that might return circular structures
139
+ if (hookAddress.hook === 'useSelector' || hookAddress.hook === 'useReduxState') {
140
+ return 'circularDeepEqual';
141
+ }
142
+
143
+ // Use deepEqual for everything else (default)
144
+ return 'deepEqual';
145
+ };
146
+ ```
147
+
148
+ The `hookAddress` parameter contains information about the hook:
149
+
150
+ ```typescript
151
+ {
152
+ hook: string; // Hook name, e.g., "useState", "useContext"
153
+ file: string; // File path where the hook is called
154
+ line: number; // Line number
155
+ offset: number; // Column offset
156
+ arguments?: string[]; // Hook arguments (if includeArguments is enabled)
157
+ }
158
+ ```
159
+
160
+ **Available Comparators:**
161
+
162
+ - `deepEqual` (default): Fast deep equality check that handles most cases. Will throw an error if it encounters deeply nested or circular structures.
163
+ - `circularDeepEqual`: Slower but handles circular references safely. Use this when your hooks return data with circular dependencies or extremely deep nesting.
164
+
165
+ **When to Use `circularDeepEqual`:**
166
+
167
+ If you see an error like "Maximum call stack size exceeded. Please use the 'circularDeepEqual' comparator", you should configure `selectComparator` to return `'circularDeepEqual'` for the specific hook mentioned in the error message.
168
+
131
169
  ## API and Configuration
132
170
 
133
171
  The `reactJitter` function accepts a configuration object with two callbacks: `onHookChange` and `onRender`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-jitter",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "A developer tool for debugging React performance issues caused by hook changes and component re-renders.",
5
5
  "exports": {
6
6
  ".": {
@@ -41,6 +41,7 @@ type HookEndEvent = {
41
41
  offset: number;
42
42
  arguments?: string[];
43
43
  };
44
+ type HookAddress = Pick<HookEndEvent, 'hook' | 'file' | 'line' | 'offset' | 'arguments'>;
44
45
  type ReactJitterOptions = {
45
46
  enabled?: boolean;
46
47
  onHookChange?: (change: HookChange) => void;
@@ -51,6 +52,7 @@ type ReactJitterOptions = {
51
52
  }) => void;
52
53
  };
53
54
  type Scope = z.infer<typeof ScopeSchema>;
55
+ type Comparator = 'deepEqual' | 'circularDeepEqual';
54
56
 
55
57
  type HookCall = HookChange & HookEndEvent & {
56
58
  scope: Scope;
@@ -62,6 +64,7 @@ declare global {
62
64
  reactJitter?: {
63
65
  enabled?: boolean;
64
66
  onHookChange?: (change: HookCall) => void;
67
+ selectComparator?: (hookAddress: HookAddress) => Comparator;
65
68
  onRender?: (scope: Scope & {
66
69
  hookResults: Record<string, unknown>;
67
70
  renderCount: number;
@@ -41,6 +41,7 @@ type HookEndEvent = {
41
41
  offset: number;
42
42
  arguments?: string[];
43
43
  };
44
+ type HookAddress = Pick<HookEndEvent, 'hook' | 'file' | 'line' | 'offset' | 'arguments'>;
44
45
  type ReactJitterOptions = {
45
46
  enabled?: boolean;
46
47
  onHookChange?: (change: HookChange) => void;
@@ -51,6 +52,7 @@ type ReactJitterOptions = {
51
52
  }) => void;
52
53
  };
53
54
  type Scope = z.infer<typeof ScopeSchema>;
55
+ type Comparator = 'deepEqual' | 'circularDeepEqual';
54
56
 
55
57
  type HookCall = HookChange & HookEndEvent & {
56
58
  scope: Scope;
@@ -62,6 +64,7 @@ declare global {
62
64
  reactJitter?: {
63
65
  enabled?: boolean;
64
66
  onHookChange?: (change: HookCall) => void;
67
+ selectComparator?: (hookAddress: HookAddress) => Comparator;
65
68
  onRender?: (scope: Scope & {
66
69
  hookResults: Record<string, unknown>;
67
70
  renderCount: number;
@@ -36,9 +36,419 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
  var import_react = __toESM(require("react"));
38
38
 
39
+ // ../node_modules/fast-equals/dist/esm/index.mjs
40
+ var getOwnPropertyNames = Object.getOwnPropertyNames;
41
+ var getOwnPropertySymbols = Object.getOwnPropertySymbols;
42
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
43
+ function combineComparators(comparatorA, comparatorB) {
44
+ return function isEqual(a, b, state) {
45
+ return comparatorA(a, b, state) && comparatorB(a, b, state);
46
+ };
47
+ }
48
+ function createIsCircular(areItemsEqual) {
49
+ return function isCircular(a, b, state) {
50
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") {
51
+ return areItemsEqual(a, b, state);
52
+ }
53
+ var cache = state.cache;
54
+ var cachedA = cache.get(a);
55
+ var cachedB = cache.get(b);
56
+ if (cachedA && cachedB) {
57
+ return cachedA === b && cachedB === a;
58
+ }
59
+ cache.set(a, b);
60
+ cache.set(b, a);
61
+ var result = areItemsEqual(a, b, state);
62
+ cache.delete(a);
63
+ cache.delete(b);
64
+ return result;
65
+ };
66
+ }
67
+ function getStrictProperties(object) {
68
+ return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));
69
+ }
70
+ var hasOwn = Object.hasOwn || function(object, property) {
71
+ return hasOwnProperty.call(object, property);
72
+ };
73
+ function sameValueZeroEqual(a, b) {
74
+ return a === b || !a && !b && a !== a && b !== b;
75
+ }
76
+ var PREACT_VNODE = "__v";
77
+ var PREACT_OWNER = "__o";
78
+ var REACT_OWNER = "_owner";
79
+ var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
80
+ var keys = Object.keys;
81
+ function areArraysEqual(a, b, state) {
82
+ var index = a.length;
83
+ if (b.length !== index) {
84
+ return false;
85
+ }
86
+ while (index-- > 0) {
87
+ if (!state.equals(a[index], b[index], index, index, a, b, state)) {
88
+ return false;
89
+ }
90
+ }
91
+ return true;
92
+ }
93
+ function areDatesEqual(a, b) {
94
+ return sameValueZeroEqual(a.getTime(), b.getTime());
95
+ }
96
+ function areErrorsEqual(a, b) {
97
+ return a.name === b.name && a.message === b.message && a.cause === b.cause && a.stack === b.stack;
98
+ }
99
+ function areFunctionsEqual(a, b) {
100
+ return a === b;
101
+ }
102
+ function areMapsEqual(a, b, state) {
103
+ var size = a.size;
104
+ if (size !== b.size) {
105
+ return false;
106
+ }
107
+ if (!size) {
108
+ return true;
109
+ }
110
+ var matchedIndices = new Array(size);
111
+ var aIterable = a.entries();
112
+ var aResult;
113
+ var bResult;
114
+ var index = 0;
115
+ while (aResult = aIterable.next()) {
116
+ if (aResult.done) {
117
+ break;
118
+ }
119
+ var bIterable = b.entries();
120
+ var hasMatch = false;
121
+ var matchIndex = 0;
122
+ while (bResult = bIterable.next()) {
123
+ if (bResult.done) {
124
+ break;
125
+ }
126
+ if (matchedIndices[matchIndex]) {
127
+ matchIndex++;
128
+ continue;
129
+ }
130
+ var aEntry = aResult.value;
131
+ var bEntry = bResult.value;
132
+ if (state.equals(aEntry[0], bEntry[0], index, matchIndex, a, b, state) && state.equals(aEntry[1], bEntry[1], aEntry[0], bEntry[0], a, b, state)) {
133
+ hasMatch = matchedIndices[matchIndex] = true;
134
+ break;
135
+ }
136
+ matchIndex++;
137
+ }
138
+ if (!hasMatch) {
139
+ return false;
140
+ }
141
+ index++;
142
+ }
143
+ return true;
144
+ }
145
+ var areNumbersEqual = sameValueZeroEqual;
146
+ function areObjectsEqual(a, b, state) {
147
+ var properties = keys(a);
148
+ var index = properties.length;
149
+ if (keys(b).length !== index) {
150
+ return false;
151
+ }
152
+ while (index-- > 0) {
153
+ if (!isPropertyEqual(a, b, state, properties[index])) {
154
+ return false;
155
+ }
156
+ }
157
+ return true;
158
+ }
159
+ function areObjectsEqualStrict(a, b, state) {
160
+ var properties = getStrictProperties(a);
161
+ var index = properties.length;
162
+ if (getStrictProperties(b).length !== index) {
163
+ return false;
164
+ }
165
+ var property;
166
+ var descriptorA;
167
+ var descriptorB;
168
+ while (index-- > 0) {
169
+ property = properties[index];
170
+ if (!isPropertyEqual(a, b, state, property)) {
171
+ return false;
172
+ }
173
+ descriptorA = getOwnPropertyDescriptor(a, property);
174
+ descriptorB = getOwnPropertyDescriptor(b, property);
175
+ if ((descriptorA || descriptorB) && (!descriptorA || !descriptorB || descriptorA.configurable !== descriptorB.configurable || descriptorA.enumerable !== descriptorB.enumerable || descriptorA.writable !== descriptorB.writable)) {
176
+ return false;
177
+ }
178
+ }
179
+ return true;
180
+ }
181
+ function arePrimitiveWrappersEqual(a, b) {
182
+ return sameValueZeroEqual(a.valueOf(), b.valueOf());
183
+ }
184
+ function areRegExpsEqual(a, b) {
185
+ return a.source === b.source && a.flags === b.flags;
186
+ }
187
+ function areSetsEqual(a, b, state) {
188
+ var size = a.size;
189
+ if (size !== b.size) {
190
+ return false;
191
+ }
192
+ if (!size) {
193
+ return true;
194
+ }
195
+ var matchedIndices = new Array(size);
196
+ var aIterable = a.values();
197
+ var aResult;
198
+ var bResult;
199
+ while (aResult = aIterable.next()) {
200
+ if (aResult.done) {
201
+ break;
202
+ }
203
+ var bIterable = b.values();
204
+ var hasMatch = false;
205
+ var matchIndex = 0;
206
+ while (bResult = bIterable.next()) {
207
+ if (bResult.done) {
208
+ break;
209
+ }
210
+ if (!matchedIndices[matchIndex] && state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state)) {
211
+ hasMatch = matchedIndices[matchIndex] = true;
212
+ break;
213
+ }
214
+ matchIndex++;
215
+ }
216
+ if (!hasMatch) {
217
+ return false;
218
+ }
219
+ }
220
+ return true;
221
+ }
222
+ function areTypedArraysEqual(a, b) {
223
+ var index = a.length;
224
+ if (b.length !== index) {
225
+ return false;
226
+ }
227
+ while (index-- > 0) {
228
+ if (a[index] !== b[index]) {
229
+ return false;
230
+ }
231
+ }
232
+ return true;
233
+ }
234
+ function areUrlsEqual(a, b) {
235
+ return a.hostname === b.hostname && a.pathname === b.pathname && a.protocol === b.protocol && a.port === b.port && a.hash === b.hash && a.username === b.username && a.password === b.password;
236
+ }
237
+ function isPropertyEqual(a, b, state, property) {
238
+ if ((property === REACT_OWNER || property === PREACT_OWNER || property === PREACT_VNODE) && (a.$$typeof || b.$$typeof)) {
239
+ return true;
240
+ }
241
+ return hasOwn(b, property) && state.equals(a[property], b[property], property, property, a, b, state);
242
+ }
243
+ var ARGUMENTS_TAG = "[object Arguments]";
244
+ var BOOLEAN_TAG = "[object Boolean]";
245
+ var DATE_TAG = "[object Date]";
246
+ var ERROR_TAG = "[object Error]";
247
+ var MAP_TAG = "[object Map]";
248
+ var NUMBER_TAG = "[object Number]";
249
+ var OBJECT_TAG = "[object Object]";
250
+ var REG_EXP_TAG = "[object RegExp]";
251
+ var SET_TAG = "[object Set]";
252
+ var STRING_TAG = "[object String]";
253
+ var URL_TAG = "[object URL]";
254
+ var isArray = Array.isArray;
255
+ var isTypedArray = typeof ArrayBuffer === "function" && ArrayBuffer.isView ? ArrayBuffer.isView : null;
256
+ var assign = Object.assign;
257
+ var getTag = Object.prototype.toString.call.bind(Object.prototype.toString);
258
+ function createEqualityComparator(_a) {
259
+ var areArraysEqual2 = _a.areArraysEqual, areDatesEqual2 = _a.areDatesEqual, areErrorsEqual2 = _a.areErrorsEqual, areFunctionsEqual2 = _a.areFunctionsEqual, areMapsEqual2 = _a.areMapsEqual, areNumbersEqual2 = _a.areNumbersEqual, areObjectsEqual2 = _a.areObjectsEqual, arePrimitiveWrappersEqual2 = _a.arePrimitiveWrappersEqual, areRegExpsEqual2 = _a.areRegExpsEqual, areSetsEqual2 = _a.areSetsEqual, areTypedArraysEqual2 = _a.areTypedArraysEqual, areUrlsEqual2 = _a.areUrlsEqual;
260
+ return function comparator(a, b, state) {
261
+ if (a === b) {
262
+ return true;
263
+ }
264
+ if (a == null || b == null) {
265
+ return false;
266
+ }
267
+ var type = typeof a;
268
+ if (type !== typeof b) {
269
+ return false;
270
+ }
271
+ if (type !== "object") {
272
+ if (type === "number") {
273
+ return areNumbersEqual2(a, b, state);
274
+ }
275
+ if (type === "function") {
276
+ return areFunctionsEqual2(a, b, state);
277
+ }
278
+ return false;
279
+ }
280
+ var constructor = a.constructor;
281
+ if (constructor !== b.constructor) {
282
+ return false;
283
+ }
284
+ if (constructor === Object) {
285
+ return areObjectsEqual2(a, b, state);
286
+ }
287
+ if (isArray(a)) {
288
+ return areArraysEqual2(a, b, state);
289
+ }
290
+ if (isTypedArray != null && isTypedArray(a)) {
291
+ return areTypedArraysEqual2(a, b, state);
292
+ }
293
+ if (constructor === Date) {
294
+ return areDatesEqual2(a, b, state);
295
+ }
296
+ if (constructor === RegExp) {
297
+ return areRegExpsEqual2(a, b, state);
298
+ }
299
+ if (constructor === Map) {
300
+ return areMapsEqual2(a, b, state);
301
+ }
302
+ if (constructor === Set) {
303
+ return areSetsEqual2(a, b, state);
304
+ }
305
+ var tag = getTag(a);
306
+ if (tag === DATE_TAG) {
307
+ return areDatesEqual2(a, b, state);
308
+ }
309
+ if (tag === REG_EXP_TAG) {
310
+ return areRegExpsEqual2(a, b, state);
311
+ }
312
+ if (tag === MAP_TAG) {
313
+ return areMapsEqual2(a, b, state);
314
+ }
315
+ if (tag === SET_TAG) {
316
+ return areSetsEqual2(a, b, state);
317
+ }
318
+ if (tag === OBJECT_TAG) {
319
+ return typeof a.then !== "function" && typeof b.then !== "function" && areObjectsEqual2(a, b, state);
320
+ }
321
+ if (tag === URL_TAG) {
322
+ return areUrlsEqual2(a, b, state);
323
+ }
324
+ if (tag === ERROR_TAG) {
325
+ return areErrorsEqual2(a, b, state);
326
+ }
327
+ if (tag === ARGUMENTS_TAG) {
328
+ return areObjectsEqual2(a, b, state);
329
+ }
330
+ if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {
331
+ return arePrimitiveWrappersEqual2(a, b, state);
332
+ }
333
+ return false;
334
+ };
335
+ }
336
+ function createEqualityComparatorConfig(_a) {
337
+ var circular = _a.circular, createCustomConfig = _a.createCustomConfig, strict = _a.strict;
338
+ var config = {
339
+ areArraysEqual: strict ? areObjectsEqualStrict : areArraysEqual,
340
+ areDatesEqual,
341
+ areErrorsEqual,
342
+ areFunctionsEqual,
343
+ areMapsEqual: strict ? combineComparators(areMapsEqual, areObjectsEqualStrict) : areMapsEqual,
344
+ areNumbersEqual,
345
+ areObjectsEqual: strict ? areObjectsEqualStrict : areObjectsEqual,
346
+ arePrimitiveWrappersEqual,
347
+ areRegExpsEqual,
348
+ areSetsEqual: strict ? combineComparators(areSetsEqual, areObjectsEqualStrict) : areSetsEqual,
349
+ areTypedArraysEqual: strict ? areObjectsEqualStrict : areTypedArraysEqual,
350
+ areUrlsEqual
351
+ };
352
+ if (createCustomConfig) {
353
+ config = assign({}, config, createCustomConfig(config));
354
+ }
355
+ if (circular) {
356
+ var areArraysEqual$1 = createIsCircular(config.areArraysEqual);
357
+ var areMapsEqual$1 = createIsCircular(config.areMapsEqual);
358
+ var areObjectsEqual$1 = createIsCircular(config.areObjectsEqual);
359
+ var areSetsEqual$1 = createIsCircular(config.areSetsEqual);
360
+ config = assign({}, config, {
361
+ areArraysEqual: areArraysEqual$1,
362
+ areMapsEqual: areMapsEqual$1,
363
+ areObjectsEqual: areObjectsEqual$1,
364
+ areSetsEqual: areSetsEqual$1
365
+ });
366
+ }
367
+ return config;
368
+ }
369
+ function createInternalEqualityComparator(compare) {
370
+ return function(a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {
371
+ return compare(a, b, state);
372
+ };
373
+ }
374
+ function createIsEqual(_a) {
375
+ var circular = _a.circular, comparator = _a.comparator, createState = _a.createState, equals = _a.equals, strict = _a.strict;
376
+ if (createState) {
377
+ return function isEqual(a, b) {
378
+ var _a2 = createState(), _b = _a2.cache, cache = _b === void 0 ? circular ? /* @__PURE__ */ new WeakMap() : void 0 : _b, meta = _a2.meta;
379
+ return comparator(a, b, {
380
+ cache,
381
+ equals,
382
+ meta,
383
+ strict
384
+ });
385
+ };
386
+ }
387
+ if (circular) {
388
+ return function isEqual(a, b) {
389
+ return comparator(a, b, {
390
+ cache: /* @__PURE__ */ new WeakMap(),
391
+ equals,
392
+ meta: void 0,
393
+ strict
394
+ });
395
+ };
396
+ }
397
+ var state = {
398
+ cache: void 0,
399
+ equals,
400
+ meta: void 0,
401
+ strict
402
+ };
403
+ return function isEqual(a, b) {
404
+ return comparator(a, b, state);
405
+ };
406
+ }
407
+ var deepEqual = createCustomEqual();
408
+ var strictDeepEqual = createCustomEqual({ strict: true });
409
+ var circularDeepEqual = createCustomEqual({ circular: true });
410
+ var strictCircularDeepEqual = createCustomEqual({
411
+ circular: true,
412
+ strict: true
413
+ });
414
+ var shallowEqual = createCustomEqual({
415
+ createInternalComparator: function() {
416
+ return sameValueZeroEqual;
417
+ }
418
+ });
419
+ var strictShallowEqual = createCustomEqual({
420
+ strict: true,
421
+ createInternalComparator: function() {
422
+ return sameValueZeroEqual;
423
+ }
424
+ });
425
+ var circularShallowEqual = createCustomEqual({
426
+ circular: true,
427
+ createInternalComparator: function() {
428
+ return sameValueZeroEqual;
429
+ }
430
+ });
431
+ var strictCircularShallowEqual = createCustomEqual({
432
+ circular: true,
433
+ createInternalComparator: function() {
434
+ return sameValueZeroEqual;
435
+ },
436
+ strict: true
437
+ });
438
+ function createCustomEqual(options) {
439
+ if (options === void 0) {
440
+ options = {};
441
+ }
442
+ var _a = options.circular, circular = _a === void 0 ? false : _a, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _b = options.strict, strict = _b === void 0 ? false : _b;
443
+ var config = createEqualityComparatorConfig(options);
444
+ var comparator = createEqualityComparator(config);
445
+ var equals = createCustomInternalComparator ? createCustomInternalComparator(comparator) : createInternalEqualityComparator(comparator);
446
+ return createIsEqual({ circular, comparator, createState, equals, strict });
447
+ }
448
+
39
449
  // src/utils/getChanges.ts
40
- var import_fast_equals = require("fast-equals");
41
- function getChanges(prev, next) {
450
+ function getChanges(prev, next, comparator = "deepEqual") {
451
+ const equals = comparator === "circularDeepEqual" ? circularDeepEqual : deepEqual;
42
452
  const changedKeys = [];
43
453
  const unstableKeys = [];
44
454
  const isObject = (v) => v !== null && typeof v === "object";
@@ -57,7 +467,7 @@ function getChanges(prev, next) {
57
467
  }
58
468
  const max = Math.max(prev.length, next.length);
59
469
  for (let i = 0; i < max; i++) {
60
- const deepEqItem = (0, import_fast_equals.deepEqual)(prev[i], next[i]);
470
+ const deepEqItem = equals(prev[i], next[i]);
61
471
  const refDiffItem = isObject(prev[i]) && isObject(next[i]) && prev[i] !== next[i];
62
472
  if (!deepEqItem || refDiffItem) {
63
473
  const key = String(i);
@@ -70,7 +480,7 @@ function getChanges(prev, next) {
70
480
  } else if (isObject(prev) && isObject(next)) {
71
481
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
72
482
  for (const key of allKeys) {
73
- const deepEqProp = (0, import_fast_equals.deepEqual)(prev[key], next[key]);
483
+ const deepEqProp = equals(prev[key], next[key]);
74
484
  const refDiffProp = isObject(prev[key]) && isObject(next[key]) && prev[key] !== next[key];
75
485
  if (!deepEqProp || refDiffProp) {
76
486
  changedKeys.push(key);
@@ -80,7 +490,7 @@ function getChanges(prev, next) {
80
490
  }
81
491
  }
82
492
  } else {
83
- const deepEqRoot = (0, import_fast_equals.deepEqual)(prev, next);
493
+ const deepEqRoot = equals(prev, next);
84
494
  const refDiffRoot = isObject(prev) && isObject(next) && prev !== next;
85
495
  const unstable = refDiffRoot && deepEqRoot;
86
496
  const changed = !deepEqRoot || refDiffRoot;
@@ -91,7 +501,7 @@ function getChanges(prev, next) {
91
501
  };
92
502
  }
93
503
  const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
94
- const unstableRoot = isPlainObject(prev) && isPlainObject(next) && prev !== next && (0, import_fast_equals.deepEqual)(prev, next);
504
+ const unstableRoot = isPlainObject(prev) && isPlainObject(next) && prev !== next && equals(prev, next);
95
505
  if (unstableRoot && changedKeys.length === 0) {
96
506
  changedKeys.push("");
97
507
  unstableKeys.push("");
@@ -103,6 +513,31 @@ function getChanges(prev, next) {
103
513
  };
104
514
  }
105
515
 
516
+ // src/utils/compareChanges.ts
517
+ function compareChanges(hookAddress, prev, current) {
518
+ var _a, _b, _c;
519
+ if (prev !== "undefined" && prev !== current) {
520
+ const comparator = (_c = (_b = (_a = window == null ? void 0 : window.reactJitter) == null ? void 0 : _a.selectComparator) == null ? void 0 : _b.call(_a, hookAddress)) != null ? _c : "deepEqual";
521
+ try {
522
+ return getChanges(prev, current, comparator);
523
+ } catch (error) {
524
+ const errorMessage = error instanceof Error ? error.message : String(error);
525
+ const isRecursionError = /(?:maximum call stack(?: size)? exceeded|too much recursion|stack overflow)/i.test(
526
+ errorMessage
527
+ );
528
+ if (isRecursionError && comparator !== "circularDeepEqual") {
529
+ throw new Error(
530
+ `Maximum call stack size exceeded. Please use the "circularDeepEqual" comparator with selectComparator option.
531
+ Hook address: ${JSON.stringify(hookAddress, null, 2)}.`,
532
+ { cause: error }
533
+ );
534
+ }
535
+ throw error;
536
+ }
537
+ }
538
+ return null;
539
+ }
540
+
106
541
  // src/index.ts
107
542
  var scopes = {};
108
543
  var hookStack = /* @__PURE__ */ new Map();
@@ -135,7 +570,13 @@ function useJitterScope(scope) {
135
570
  const hookId = `${scopeId}-${hookEndEvent.id}`;
136
571
  if (shouldReportChanges()) {
137
572
  const prevResult = currentScope.hookResults[hookId];
138
- const changes = compareChanges(prevResult, hookResult);
573
+ const hookAddress = {
574
+ hook: hookEndEvent.hook,
575
+ file: hookEndEvent.file,
576
+ line: hookEndEvent.line,
577
+ offset: hookEndEvent.offset
578
+ };
579
+ const changes = compareChanges(hookAddress, prevResult, hookResult);
139
580
  if (changes) {
140
581
  const hookCall = {
141
582
  hook: hookEndEvent.hook,
@@ -212,12 +653,6 @@ function getScopeCount(scope) {
212
653
  }
213
654
  return scopeCounter[scope.id]++;
214
655
  }
215
- function compareChanges(prev, current) {
216
- if (prev !== "undefined" && prev !== current) {
217
- return getChanges(prev, current);
218
- }
219
- return null;
220
- }
221
656
  // Annotate the CommonJS export names for ESM import in node:
222
657
  0 && (module.exports = {
223
658
  reactJitter,
@@ -1,9 +1,419 @@
1
1
  // src/index.ts
2
2
  import React from "react";
3
3
 
4
+ // ../node_modules/fast-equals/dist/esm/index.mjs
5
+ var getOwnPropertyNames = Object.getOwnPropertyNames;
6
+ var getOwnPropertySymbols = Object.getOwnPropertySymbols;
7
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
8
+ function combineComparators(comparatorA, comparatorB) {
9
+ return function isEqual(a, b, state) {
10
+ return comparatorA(a, b, state) && comparatorB(a, b, state);
11
+ };
12
+ }
13
+ function createIsCircular(areItemsEqual) {
14
+ return function isCircular(a, b, state) {
15
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") {
16
+ return areItemsEqual(a, b, state);
17
+ }
18
+ var cache = state.cache;
19
+ var cachedA = cache.get(a);
20
+ var cachedB = cache.get(b);
21
+ if (cachedA && cachedB) {
22
+ return cachedA === b && cachedB === a;
23
+ }
24
+ cache.set(a, b);
25
+ cache.set(b, a);
26
+ var result = areItemsEqual(a, b, state);
27
+ cache.delete(a);
28
+ cache.delete(b);
29
+ return result;
30
+ };
31
+ }
32
+ function getStrictProperties(object) {
33
+ return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));
34
+ }
35
+ var hasOwn = Object.hasOwn || function(object, property) {
36
+ return hasOwnProperty.call(object, property);
37
+ };
38
+ function sameValueZeroEqual(a, b) {
39
+ return a === b || !a && !b && a !== a && b !== b;
40
+ }
41
+ var PREACT_VNODE = "__v";
42
+ var PREACT_OWNER = "__o";
43
+ var REACT_OWNER = "_owner";
44
+ var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
45
+ var keys = Object.keys;
46
+ function areArraysEqual(a, b, state) {
47
+ var index = a.length;
48
+ if (b.length !== index) {
49
+ return false;
50
+ }
51
+ while (index-- > 0) {
52
+ if (!state.equals(a[index], b[index], index, index, a, b, state)) {
53
+ return false;
54
+ }
55
+ }
56
+ return true;
57
+ }
58
+ function areDatesEqual(a, b) {
59
+ return sameValueZeroEqual(a.getTime(), b.getTime());
60
+ }
61
+ function areErrorsEqual(a, b) {
62
+ return a.name === b.name && a.message === b.message && a.cause === b.cause && a.stack === b.stack;
63
+ }
64
+ function areFunctionsEqual(a, b) {
65
+ return a === b;
66
+ }
67
+ function areMapsEqual(a, b, state) {
68
+ var size = a.size;
69
+ if (size !== b.size) {
70
+ return false;
71
+ }
72
+ if (!size) {
73
+ return true;
74
+ }
75
+ var matchedIndices = new Array(size);
76
+ var aIterable = a.entries();
77
+ var aResult;
78
+ var bResult;
79
+ var index = 0;
80
+ while (aResult = aIterable.next()) {
81
+ if (aResult.done) {
82
+ break;
83
+ }
84
+ var bIterable = b.entries();
85
+ var hasMatch = false;
86
+ var matchIndex = 0;
87
+ while (bResult = bIterable.next()) {
88
+ if (bResult.done) {
89
+ break;
90
+ }
91
+ if (matchedIndices[matchIndex]) {
92
+ matchIndex++;
93
+ continue;
94
+ }
95
+ var aEntry = aResult.value;
96
+ var bEntry = bResult.value;
97
+ if (state.equals(aEntry[0], bEntry[0], index, matchIndex, a, b, state) && state.equals(aEntry[1], bEntry[1], aEntry[0], bEntry[0], a, b, state)) {
98
+ hasMatch = matchedIndices[matchIndex] = true;
99
+ break;
100
+ }
101
+ matchIndex++;
102
+ }
103
+ if (!hasMatch) {
104
+ return false;
105
+ }
106
+ index++;
107
+ }
108
+ return true;
109
+ }
110
+ var areNumbersEqual = sameValueZeroEqual;
111
+ function areObjectsEqual(a, b, state) {
112
+ var properties = keys(a);
113
+ var index = properties.length;
114
+ if (keys(b).length !== index) {
115
+ return false;
116
+ }
117
+ while (index-- > 0) {
118
+ if (!isPropertyEqual(a, b, state, properties[index])) {
119
+ return false;
120
+ }
121
+ }
122
+ return true;
123
+ }
124
+ function areObjectsEqualStrict(a, b, state) {
125
+ var properties = getStrictProperties(a);
126
+ var index = properties.length;
127
+ if (getStrictProperties(b).length !== index) {
128
+ return false;
129
+ }
130
+ var property;
131
+ var descriptorA;
132
+ var descriptorB;
133
+ while (index-- > 0) {
134
+ property = properties[index];
135
+ if (!isPropertyEqual(a, b, state, property)) {
136
+ return false;
137
+ }
138
+ descriptorA = getOwnPropertyDescriptor(a, property);
139
+ descriptorB = getOwnPropertyDescriptor(b, property);
140
+ if ((descriptorA || descriptorB) && (!descriptorA || !descriptorB || descriptorA.configurable !== descriptorB.configurable || descriptorA.enumerable !== descriptorB.enumerable || descriptorA.writable !== descriptorB.writable)) {
141
+ return false;
142
+ }
143
+ }
144
+ return true;
145
+ }
146
+ function arePrimitiveWrappersEqual(a, b) {
147
+ return sameValueZeroEqual(a.valueOf(), b.valueOf());
148
+ }
149
+ function areRegExpsEqual(a, b) {
150
+ return a.source === b.source && a.flags === b.flags;
151
+ }
152
+ function areSetsEqual(a, b, state) {
153
+ var size = a.size;
154
+ if (size !== b.size) {
155
+ return false;
156
+ }
157
+ if (!size) {
158
+ return true;
159
+ }
160
+ var matchedIndices = new Array(size);
161
+ var aIterable = a.values();
162
+ var aResult;
163
+ var bResult;
164
+ while (aResult = aIterable.next()) {
165
+ if (aResult.done) {
166
+ break;
167
+ }
168
+ var bIterable = b.values();
169
+ var hasMatch = false;
170
+ var matchIndex = 0;
171
+ while (bResult = bIterable.next()) {
172
+ if (bResult.done) {
173
+ break;
174
+ }
175
+ if (!matchedIndices[matchIndex] && state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state)) {
176
+ hasMatch = matchedIndices[matchIndex] = true;
177
+ break;
178
+ }
179
+ matchIndex++;
180
+ }
181
+ if (!hasMatch) {
182
+ return false;
183
+ }
184
+ }
185
+ return true;
186
+ }
187
+ function areTypedArraysEqual(a, b) {
188
+ var index = a.length;
189
+ if (b.length !== index) {
190
+ return false;
191
+ }
192
+ while (index-- > 0) {
193
+ if (a[index] !== b[index]) {
194
+ return false;
195
+ }
196
+ }
197
+ return true;
198
+ }
199
+ function areUrlsEqual(a, b) {
200
+ return a.hostname === b.hostname && a.pathname === b.pathname && a.protocol === b.protocol && a.port === b.port && a.hash === b.hash && a.username === b.username && a.password === b.password;
201
+ }
202
+ function isPropertyEqual(a, b, state, property) {
203
+ if ((property === REACT_OWNER || property === PREACT_OWNER || property === PREACT_VNODE) && (a.$$typeof || b.$$typeof)) {
204
+ return true;
205
+ }
206
+ return hasOwn(b, property) && state.equals(a[property], b[property], property, property, a, b, state);
207
+ }
208
+ var ARGUMENTS_TAG = "[object Arguments]";
209
+ var BOOLEAN_TAG = "[object Boolean]";
210
+ var DATE_TAG = "[object Date]";
211
+ var ERROR_TAG = "[object Error]";
212
+ var MAP_TAG = "[object Map]";
213
+ var NUMBER_TAG = "[object Number]";
214
+ var OBJECT_TAG = "[object Object]";
215
+ var REG_EXP_TAG = "[object RegExp]";
216
+ var SET_TAG = "[object Set]";
217
+ var STRING_TAG = "[object String]";
218
+ var URL_TAG = "[object URL]";
219
+ var isArray = Array.isArray;
220
+ var isTypedArray = typeof ArrayBuffer === "function" && ArrayBuffer.isView ? ArrayBuffer.isView : null;
221
+ var assign = Object.assign;
222
+ var getTag = Object.prototype.toString.call.bind(Object.prototype.toString);
223
+ function createEqualityComparator(_a) {
224
+ var areArraysEqual2 = _a.areArraysEqual, areDatesEqual2 = _a.areDatesEqual, areErrorsEqual2 = _a.areErrorsEqual, areFunctionsEqual2 = _a.areFunctionsEqual, areMapsEqual2 = _a.areMapsEqual, areNumbersEqual2 = _a.areNumbersEqual, areObjectsEqual2 = _a.areObjectsEqual, arePrimitiveWrappersEqual2 = _a.arePrimitiveWrappersEqual, areRegExpsEqual2 = _a.areRegExpsEqual, areSetsEqual2 = _a.areSetsEqual, areTypedArraysEqual2 = _a.areTypedArraysEqual, areUrlsEqual2 = _a.areUrlsEqual;
225
+ return function comparator(a, b, state) {
226
+ if (a === b) {
227
+ return true;
228
+ }
229
+ if (a == null || b == null) {
230
+ return false;
231
+ }
232
+ var type = typeof a;
233
+ if (type !== typeof b) {
234
+ return false;
235
+ }
236
+ if (type !== "object") {
237
+ if (type === "number") {
238
+ return areNumbersEqual2(a, b, state);
239
+ }
240
+ if (type === "function") {
241
+ return areFunctionsEqual2(a, b, state);
242
+ }
243
+ return false;
244
+ }
245
+ var constructor = a.constructor;
246
+ if (constructor !== b.constructor) {
247
+ return false;
248
+ }
249
+ if (constructor === Object) {
250
+ return areObjectsEqual2(a, b, state);
251
+ }
252
+ if (isArray(a)) {
253
+ return areArraysEqual2(a, b, state);
254
+ }
255
+ if (isTypedArray != null && isTypedArray(a)) {
256
+ return areTypedArraysEqual2(a, b, state);
257
+ }
258
+ if (constructor === Date) {
259
+ return areDatesEqual2(a, b, state);
260
+ }
261
+ if (constructor === RegExp) {
262
+ return areRegExpsEqual2(a, b, state);
263
+ }
264
+ if (constructor === Map) {
265
+ return areMapsEqual2(a, b, state);
266
+ }
267
+ if (constructor === Set) {
268
+ return areSetsEqual2(a, b, state);
269
+ }
270
+ var tag = getTag(a);
271
+ if (tag === DATE_TAG) {
272
+ return areDatesEqual2(a, b, state);
273
+ }
274
+ if (tag === REG_EXP_TAG) {
275
+ return areRegExpsEqual2(a, b, state);
276
+ }
277
+ if (tag === MAP_TAG) {
278
+ return areMapsEqual2(a, b, state);
279
+ }
280
+ if (tag === SET_TAG) {
281
+ return areSetsEqual2(a, b, state);
282
+ }
283
+ if (tag === OBJECT_TAG) {
284
+ return typeof a.then !== "function" && typeof b.then !== "function" && areObjectsEqual2(a, b, state);
285
+ }
286
+ if (tag === URL_TAG) {
287
+ return areUrlsEqual2(a, b, state);
288
+ }
289
+ if (tag === ERROR_TAG) {
290
+ return areErrorsEqual2(a, b, state);
291
+ }
292
+ if (tag === ARGUMENTS_TAG) {
293
+ return areObjectsEqual2(a, b, state);
294
+ }
295
+ if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {
296
+ return arePrimitiveWrappersEqual2(a, b, state);
297
+ }
298
+ return false;
299
+ };
300
+ }
301
+ function createEqualityComparatorConfig(_a) {
302
+ var circular = _a.circular, createCustomConfig = _a.createCustomConfig, strict = _a.strict;
303
+ var config = {
304
+ areArraysEqual: strict ? areObjectsEqualStrict : areArraysEqual,
305
+ areDatesEqual,
306
+ areErrorsEqual,
307
+ areFunctionsEqual,
308
+ areMapsEqual: strict ? combineComparators(areMapsEqual, areObjectsEqualStrict) : areMapsEqual,
309
+ areNumbersEqual,
310
+ areObjectsEqual: strict ? areObjectsEqualStrict : areObjectsEqual,
311
+ arePrimitiveWrappersEqual,
312
+ areRegExpsEqual,
313
+ areSetsEqual: strict ? combineComparators(areSetsEqual, areObjectsEqualStrict) : areSetsEqual,
314
+ areTypedArraysEqual: strict ? areObjectsEqualStrict : areTypedArraysEqual,
315
+ areUrlsEqual
316
+ };
317
+ if (createCustomConfig) {
318
+ config = assign({}, config, createCustomConfig(config));
319
+ }
320
+ if (circular) {
321
+ var areArraysEqual$1 = createIsCircular(config.areArraysEqual);
322
+ var areMapsEqual$1 = createIsCircular(config.areMapsEqual);
323
+ var areObjectsEqual$1 = createIsCircular(config.areObjectsEqual);
324
+ var areSetsEqual$1 = createIsCircular(config.areSetsEqual);
325
+ config = assign({}, config, {
326
+ areArraysEqual: areArraysEqual$1,
327
+ areMapsEqual: areMapsEqual$1,
328
+ areObjectsEqual: areObjectsEqual$1,
329
+ areSetsEqual: areSetsEqual$1
330
+ });
331
+ }
332
+ return config;
333
+ }
334
+ function createInternalEqualityComparator(compare) {
335
+ return function(a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {
336
+ return compare(a, b, state);
337
+ };
338
+ }
339
+ function createIsEqual(_a) {
340
+ var circular = _a.circular, comparator = _a.comparator, createState = _a.createState, equals = _a.equals, strict = _a.strict;
341
+ if (createState) {
342
+ return function isEqual(a, b) {
343
+ var _a2 = createState(), _b = _a2.cache, cache = _b === void 0 ? circular ? /* @__PURE__ */ new WeakMap() : void 0 : _b, meta = _a2.meta;
344
+ return comparator(a, b, {
345
+ cache,
346
+ equals,
347
+ meta,
348
+ strict
349
+ });
350
+ };
351
+ }
352
+ if (circular) {
353
+ return function isEqual(a, b) {
354
+ return comparator(a, b, {
355
+ cache: /* @__PURE__ */ new WeakMap(),
356
+ equals,
357
+ meta: void 0,
358
+ strict
359
+ });
360
+ };
361
+ }
362
+ var state = {
363
+ cache: void 0,
364
+ equals,
365
+ meta: void 0,
366
+ strict
367
+ };
368
+ return function isEqual(a, b) {
369
+ return comparator(a, b, state);
370
+ };
371
+ }
372
+ var deepEqual = createCustomEqual();
373
+ var strictDeepEqual = createCustomEqual({ strict: true });
374
+ var circularDeepEqual = createCustomEqual({ circular: true });
375
+ var strictCircularDeepEqual = createCustomEqual({
376
+ circular: true,
377
+ strict: true
378
+ });
379
+ var shallowEqual = createCustomEqual({
380
+ createInternalComparator: function() {
381
+ return sameValueZeroEqual;
382
+ }
383
+ });
384
+ var strictShallowEqual = createCustomEqual({
385
+ strict: true,
386
+ createInternalComparator: function() {
387
+ return sameValueZeroEqual;
388
+ }
389
+ });
390
+ var circularShallowEqual = createCustomEqual({
391
+ circular: true,
392
+ createInternalComparator: function() {
393
+ return sameValueZeroEqual;
394
+ }
395
+ });
396
+ var strictCircularShallowEqual = createCustomEqual({
397
+ circular: true,
398
+ createInternalComparator: function() {
399
+ return sameValueZeroEqual;
400
+ },
401
+ strict: true
402
+ });
403
+ function createCustomEqual(options) {
404
+ if (options === void 0) {
405
+ options = {};
406
+ }
407
+ var _a = options.circular, circular = _a === void 0 ? false : _a, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _b = options.strict, strict = _b === void 0 ? false : _b;
408
+ var config = createEqualityComparatorConfig(options);
409
+ var comparator = createEqualityComparator(config);
410
+ var equals = createCustomInternalComparator ? createCustomInternalComparator(comparator) : createInternalEqualityComparator(comparator);
411
+ return createIsEqual({ circular, comparator, createState, equals, strict });
412
+ }
413
+
4
414
  // src/utils/getChanges.ts
5
- import { deepEqual } from "fast-equals";
6
- function getChanges(prev, next) {
415
+ function getChanges(prev, next, comparator = "deepEqual") {
416
+ const equals = comparator === "circularDeepEqual" ? circularDeepEqual : deepEqual;
7
417
  const changedKeys = [];
8
418
  const unstableKeys = [];
9
419
  const isObject = (v) => v !== null && typeof v === "object";
@@ -22,7 +432,7 @@ function getChanges(prev, next) {
22
432
  }
23
433
  const max = Math.max(prev.length, next.length);
24
434
  for (let i = 0; i < max; i++) {
25
- const deepEqItem = deepEqual(prev[i], next[i]);
435
+ const deepEqItem = equals(prev[i], next[i]);
26
436
  const refDiffItem = isObject(prev[i]) && isObject(next[i]) && prev[i] !== next[i];
27
437
  if (!deepEqItem || refDiffItem) {
28
438
  const key = String(i);
@@ -35,7 +445,7 @@ function getChanges(prev, next) {
35
445
  } else if (isObject(prev) && isObject(next)) {
36
446
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
37
447
  for (const key of allKeys) {
38
- const deepEqProp = deepEqual(prev[key], next[key]);
448
+ const deepEqProp = equals(prev[key], next[key]);
39
449
  const refDiffProp = isObject(prev[key]) && isObject(next[key]) && prev[key] !== next[key];
40
450
  if (!deepEqProp || refDiffProp) {
41
451
  changedKeys.push(key);
@@ -45,7 +455,7 @@ function getChanges(prev, next) {
45
455
  }
46
456
  }
47
457
  } else {
48
- const deepEqRoot = deepEqual(prev, next);
458
+ const deepEqRoot = equals(prev, next);
49
459
  const refDiffRoot = isObject(prev) && isObject(next) && prev !== next;
50
460
  const unstable = refDiffRoot && deepEqRoot;
51
461
  const changed = !deepEqRoot || refDiffRoot;
@@ -56,7 +466,7 @@ function getChanges(prev, next) {
56
466
  };
57
467
  }
58
468
  const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
59
- const unstableRoot = isPlainObject(prev) && isPlainObject(next) && prev !== next && deepEqual(prev, next);
469
+ const unstableRoot = isPlainObject(prev) && isPlainObject(next) && prev !== next && equals(prev, next);
60
470
  if (unstableRoot && changedKeys.length === 0) {
61
471
  changedKeys.push("");
62
472
  unstableKeys.push("");
@@ -68,6 +478,31 @@ function getChanges(prev, next) {
68
478
  };
69
479
  }
70
480
 
481
+ // src/utils/compareChanges.ts
482
+ function compareChanges(hookAddress, prev, current) {
483
+ var _a, _b, _c;
484
+ if (prev !== "undefined" && prev !== current) {
485
+ const comparator = (_c = (_b = (_a = window == null ? void 0 : window.reactJitter) == null ? void 0 : _a.selectComparator) == null ? void 0 : _b.call(_a, hookAddress)) != null ? _c : "deepEqual";
486
+ try {
487
+ return getChanges(prev, current, comparator);
488
+ } catch (error) {
489
+ const errorMessage = error instanceof Error ? error.message : String(error);
490
+ const isRecursionError = /(?:maximum call stack(?: size)? exceeded|too much recursion|stack overflow)/i.test(
491
+ errorMessage
492
+ );
493
+ if (isRecursionError && comparator !== "circularDeepEqual") {
494
+ throw new Error(
495
+ `Maximum call stack size exceeded. Please use the "circularDeepEqual" comparator with selectComparator option.
496
+ Hook address: ${JSON.stringify(hookAddress, null, 2)}.`,
497
+ { cause: error }
498
+ );
499
+ }
500
+ throw error;
501
+ }
502
+ }
503
+ return null;
504
+ }
505
+
71
506
  // src/index.ts
72
507
  var scopes = {};
73
508
  var hookStack = /* @__PURE__ */ new Map();
@@ -100,7 +535,13 @@ function useJitterScope(scope) {
100
535
  const hookId = `${scopeId}-${hookEndEvent.id}`;
101
536
  if (shouldReportChanges()) {
102
537
  const prevResult = currentScope.hookResults[hookId];
103
- const changes = compareChanges(prevResult, hookResult);
538
+ const hookAddress = {
539
+ hook: hookEndEvent.hook,
540
+ file: hookEndEvent.file,
541
+ line: hookEndEvent.line,
542
+ offset: hookEndEvent.offset
543
+ };
544
+ const changes = compareChanges(hookAddress, prevResult, hookResult);
104
545
  if (changes) {
105
546
  const hookCall = {
106
547
  hook: hookEndEvent.hook,
@@ -177,12 +618,6 @@ function getScopeCount(scope) {
177
618
  }
178
619
  return scopeCounter[scope.id]++;
179
620
  }
180
- function compareChanges(prev, current) {
181
- if (prev !== "undefined" && prev !== current) {
182
- return getChanges(prev, current);
183
- }
184
- return null;
185
- }
186
621
  export {
187
622
  reactJitter,
188
623
  useJitterScope