typedash
Version:
modern, type-safe collection of utility functions
398 lines (377 loc) • 15.3 kB
JavaScript
import { t as isArray } from "./isArray-DkX32HVI.js";
import { t as zip } from "./zip-BYUd7Yr1.js";
//#region src/functions/isEqual/_internal/array/areArraysEqual.ts
/**
* Whether the arrays are equal in value.
* @param array1 The first array.
* @param array2 The second array.
* @param context The context object.
* @returns true if the two arrays are equal, false otherwise.
*/
function areArraysEqual(array1, array2, context) {
if (array1.length !== array2.length) return false;
for (const [index, [element1, element2]] of zip(array1, array2).entries()) if (!context.equals(element1, element2, index, index, array1, array2, context)) return false;
return true;
}
//#endregion
//#region src/functions/isEqual/_internal/createIsCircularTypeEqualityComparator.ts
/**
* Wrap the provided `areItemsEqual` method to manage the circular state, allowing
* for circular references to be safely included in the comparison without creating
* stack overflows.
* @param areItemsEqual The comparator to wrap.
* @returns A comparator that can handle circular references.
*/
function createIsCircularTypeEqualityComparator(areItemsEqual) {
return function isCircular(a, b, context) {
if (!a || !b || typeof a !== "object" || typeof b !== "object") return areItemsEqual(a, b, context);
const { cache } = context;
const cachedA = cache.get(a);
const cachedB = cache.get(b);
if (cachedA && cachedB) return cachedA === b && cachedB === a;
cache.set(a, b);
cache.set(b, a);
const result = areItemsEqual(a, b, context);
cache.delete(a);
cache.delete(b);
return result;
};
}
//#endregion
//#region src/functions/isEqual/_internal/date/areDatesEqual.ts
/**
* Whether the dates passed are equal in value.
* @param value1 The first date.
* @param value2 The second date.
* @returns true if the two dates are equal, false otherwise.
*/
function areDatesEqual(value1, value2) {
return Object.is(value1.getTime(), value2.getTime());
}
//#endregion
//#region src/functions/isEqual/_internal/date/dateTag.ts
const DATE_TAG = "[object Date]";
//#endregion
//#region src/functions/isEqual/_internal/map/areMapsEqual.ts
/**
* Whether the `Map`s are equal in value.
* @param value1 The first `Map`.
* @param value2 The second `Map`.
* @param context The context object.
* @returns true if the two `Map`s are equal, false otherwise.
*/
function areMapsEqual(value1, value2, context) {
if (value1.size !== value2.size) return false;
const matchedIndices = {};
let index = 0;
for (const [aKey, aValue] of value1) {
let hasMatch = false;
let matchIndex = 0;
for (const [bKey, bValue] of value2) {
if (!hasMatch && !matchedIndices[matchIndex] && (hasMatch = context.equals(aKey, bKey, index, matchIndex, value1, value2, context) && context.equals(aValue, bValue, aKey, bKey, value1, value2, context))) matchedIndices[matchIndex] = true;
matchIndex++;
}
if (!hasMatch) return false;
index++;
}
return true;
}
//#endregion
//#region src/functions/isEqual/_internal/react/isReactElement.ts
/**
* Check if an object is a React element by examining its $$typeof property.
* Supports both React 18 and React 19 element types.
*
* React <=18: Symbol.for('react.element')
* React 19: Symbol.for('react.transitional.element')
*/
function isReactElement(obj) {
return !!(obj && typeof obj === "object" && obj !== null && "$$typeof" in obj && typeof obj.$$typeof === "symbol" && (obj.$$typeof.toString().includes("react.element") || obj.$$typeof.toString().includes("react.transitional.element")));
}
//#endregion
//#region src/functions/isEqual/_internal/react/reactInternalProperties.ts
/**
* React internal properties that should be ignored during equality comparison.
* These properties can differ between logically equivalent React elements:
*
* - _owner: React component that created this element (circular reference)
* - ref: Reference to DOM node or component instance
* - _store: Internal React state storage
* - _debugInfo: React 19 debug information (dev mode)
* - _debugStack: React 19 debug stack trace (dev mode)
* - _debugTask: React 19 debug task information (dev mode)
*/
const REACT_INTERNAL_PROPERTIES = new Set([
"_owner",
"ref",
"_store",
"_debugInfo",
"_debugStack",
"_debugTask"
]);
/**
* Check if a property name is a React internal property that should be
* ignored during equality comparison.
*/
function isReactInternalProperty(propertyName) {
return typeof propertyName === "string" && REACT_INTERNAL_PROPERTIES.has(propertyName);
}
//#endregion
//#region src/functions/isEqual/_internal/object/getObjectProperties.ts
/**
* Get the properties to strictly examine, which include both own properties that are
* not enumerable and symbol properties.
* @param object The object to get the properties for.
* @returns The properties to strictly examine.
*/
function getObjectProperties(object) {
return [...Object.getOwnPropertyNames(object), ...Object.getOwnPropertySymbols(object)];
}
//#endregion
//#region src/functions/isEqual/_internal/react/areReactElementsEqual.ts
/**
* Compare two React elements for equality.
*
* React elements are considered equal if they have the same:
* - $$typeof (React element symbol)
* - type (component or tag name)
* - key
* - props (deep comparison)
*
* Internal React properties like _owner, ref, _store, and debug properties
* are ignored as they can differ between logically equivalent elements.
*/
function areReactElementsEqual(element1, element2, context) {
if (!isReactElement(element1) || !isReactElement(element2)) return false;
if (element1.$$typeof !== element2.$$typeof) return false;
const properties1 = getObjectProperties(element1);
const properties2 = getObjectProperties(element2);
const semanticProps1 = properties1.filter((prop) => !isReactInternalProperty(prop));
const semanticProps2 = properties2.filter((prop) => !isReactInternalProperty(prop));
if (semanticProps1.length !== semanticProps2.length) return false;
for (const property of properties1) {
if (isReactInternalProperty(property)) continue;
if (!Object.hasOwn(element2, property)) return false;
if (!context.equals(element1[property], element2[property], property, property, element1, element2, context)) return false;
}
return true;
}
//#endregion
//#region src/functions/isEqual/_internal/object/areObjectsEqual.ts
/**
* Whether the objects are equal in value with strict property checking.
* @param value1 The first object.
* @param value2 The second object.
* @param context The context object.
* @returns true if the two objects are equal, false otherwise.
*/
function areObjectsEqual(value1, value2, context) {
if (isReactElement(value1) && isReactElement(value2)) return areReactElementsEqual(value1, value2, context);
if (isReactElement(value1) || isReactElement(value2)) return false;
const properties = getObjectProperties(value1);
if (getObjectProperties(value2).length !== properties.length) return false;
for (const property of properties) {
if (!Object.hasOwn(value2, property)) return false;
if (!context.equals(value1[property], value2[property], property, property, value1, value2, context)) return false;
}
return true;
}
//#endregion
//#region src/functions/isEqual/_internal/primitiveWrappers/arePrimitiveWrappersEqual.ts
/**
* Whether the primitive wrappers passed are equal in value.
* @param value1 The first primitive wrapper.
* @param value2 The second primitive wrapper.
* @returns true if the two primitive wrappers are equal, false otherwise.
*/
function arePrimitiveWrappersEqual(value1, value2) {
return Object.is(value1.valueOf(), value2.valueOf());
}
//#endregion
//#region src/functions/isEqual/_internal/regExp/areRegExpsEqual.ts
/**
* Whether the regexps passed are equal in value.
* @param value1 The first regexp.
* @param value2 The second regexp.
* @returns true if the two regexps are equal, false otherwise.
*/
function areRegExpsEqual(value1, value2) {
return value1.source === value2.source && value1.flags === value2.flags;
}
//#endregion
//#region src/functions/isEqual/_internal/regExp/regExpTag.ts
const REG_EXP_TAG = "[object RegExp]";
//#endregion
//#region src/functions/isEqual/_internal/set/areSetsEqual.ts
/**
* Whether the `Set`s are equal in value.
* @param set1 The first `Set`.
* @param set2 The second `Set`.
* @param context The context object.
* @returns true if the two `Set`s are equal, false otherwise.
*/
function areSetsEqual(set1, set2, context) {
if (set1.size !== set2.size) return false;
const matchedIndices = {};
for (const element1 of set1) {
let hasMatch = false;
let matchIndex = 0;
for (const element2 of set2) {
if (!hasMatch && !matchedIndices[matchIndex] && (hasMatch = context.equals(element1, element2, element1, element2, set1, set2, context))) matchedIndices[matchIndex] = true;
matchIndex++;
}
if (!hasMatch) return false;
}
return true;
}
//#endregion
//#region src/functions/isEqual/_internal/set/setTag.ts
const SET_TAG = "[object Set]";
//#endregion
//#region src/functions/isEqual/_internal/typedArray/areTypedArraysEqual.ts
/**
* Whether the TypedArray instances are equal in value.
* @param array1 The first TypedArray instance.
* @param array2 The second TypedArray instance.
* @returns true if the two TypedArray instances are equal, false otherwise.
*/
function areTypedArraysEqual(array1, array2) {
if (array1.length !== array2.length) return false;
for (const [element1, element2] of zip(array1, array2)) if (element1 !== element2) return false;
return true;
}
//#endregion
//#region src/functions/isEqual/_internal/typedArray/isTypedArray.ts
const isTypedArray = typeof ArrayBuffer === "function" && ArrayBuffer.isView ? ArrayBuffer.isView : null;
//#endregion
//#region src/functions/isEqual/_internal/comparator.ts
const ARGUMENTS_TAG = "[object Arguments]";
const BOOLEAN_TAG = "[object Boolean]";
const MAP_TAG = "[object Map]";
const NUMBER_TAG = "[object Number]";
const OBJECT_TAG = "[object Object]";
const STRING_TAG = "[object String]";
const getTag = Object.prototype.toString.call.bind(Object.prototype.toString);
/**
* Create the configuration object used for building comparators.
*/
function createEqualityComparatorConfig() {
return {
areDatesEqual,
areRegExpsEqual,
arePrimitiveWrappersEqual,
areArraysEqual: createIsCircularTypeEqualityComparator(areArraysEqual),
areMapsEqual: createIsCircularTypeEqualityComparator(areMapsEqual),
areObjectsEqual: createIsCircularTypeEqualityComparator(areObjectsEqual),
areSetsEqual: createIsCircularTypeEqualityComparator(areSetsEqual),
areTypedArraysEqual
};
}
/**
* Default equality comparator pass-through, used as the standard `isEqual` creator for
* use inside the built comparator.
*/
function createInternalEqualityComparator(compare) {
return function internalEqualityComparator(value1, value2, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, context) {
return compare(value1, value2, context);
};
}
/**
* Create the `isEqual` function used by the consuming application.
*/
function createIsEqual({ comparator, equals }) {
return function isEqual(value1, value2) {
return comparator(value1, value2, {
cache: /* @__PURE__ */ new WeakMap(),
equals
});
};
}
//#endregion
//#region src/functions/isEqual/_internal/createEqualityComparator.ts
/**
* Create a comparator method based on the type-specific equality comparators passed.
* @param root0 The configuration object.
* @param root0.areArraysEqual The array equality comparator.
* @param root0.areDatesEqual The date equality comparator.
* @param root0.areMapsEqual The map equality comparator.
* @param root0.areObjectsEqual The object equality comparator.
* @param root0.arePrimitiveWrappersEqual The primitive wrapper equality comparator.
* @param root0.areRegExpsEqual The regular expression equality comparator.
* @param root0.areSetsEqual The set equality comparator.
* @param root0.areTypedArraysEqual The typed array equality comparator.
* @returns The comparator method.
*/
function createEqualityComparator({ areArraysEqual, areDatesEqual, areMapsEqual, areObjectsEqual, arePrimitiveWrappersEqual, areRegExpsEqual, areSetsEqual, areTypedArraysEqual }) {
/**
* compare the value of the two objects and return true if they are equivalent in values
* @param value1 the first value to compare
* @param value2 the second value to compare
* @param context the context object
* @returns true if the two values are equivalent in values
*/
return function comparator(value1, value2, context) {
if (Object.is(value1, value2) || value1 === value2) return true;
if (value1?.constructor !== value2?.constructor) return false;
if (value1.constructor === Object) return areObjectsEqual(value1, value2, context);
if (isArray(value1)) return areArraysEqual(value1, value2, context);
if (isTypedArray?.(value1)) return areTypedArraysEqual(value1, value2, context);
if (value1.constructor === Date) return areDatesEqual(value1, value2, context);
if (value1.constructor === RegExp) return areRegExpsEqual(value1, value2, context);
if (value1.constructor === Map) return areMapsEqual(value1, value2, context);
if (value1.constructor === Set) return areSetsEqual(value1, value2, context);
switch (getTag(value1)) {
case DATE_TAG: return areDatesEqual(value1, value2, context);
case REG_EXP_TAG: return areRegExpsEqual(value1, value2, context);
case MAP_TAG: return areMapsEqual(value1, value2, context);
case SET_TAG: return areSetsEqual(value1, value2, context);
case OBJECT_TAG: return typeof value1.then !== "function" && typeof value2.then !== "function" && areObjectsEqual(value1, value2, context);
case ARGUMENTS_TAG: return areObjectsEqual(value1, value2, context);
case BOOLEAN_TAG:
case NUMBER_TAG:
case STRING_TAG: return arePrimitiveWrappersEqual(value1, value2, context);
default: return false;
}
};
}
//#endregion
//#region src/functions/isEqual/isEqual.ts
/**
* Slimmed down version of https://github.com/planttheidea/fast-equals
* Without all the configuration options, and with a few tweaks to make it more readable (though a bit less performant)
*/
/**
* Compare two values to determine if they are deeply equivalent by value.
* @param value1 The first value to compare.
* @param value2 The second value to compare.
* @returns `true` if the two values are equivalent, `false` otherwise.
* @example
* ```ts
* isEqual(1, 1); // true
* isEqual(1, '1'); // false
* isEqual({ a: 1 }, { a: 1 }); // true
* isEqual({ a: 1 }, { a: 2 }); // false
* isEqual(new Date('2020-01-01'), new Date('2020-01-01')); // true
* isEqual(new Set([1, 2, 3]), new Set([3, 2, 1])); // true
* ```
*/
const isEqual = createCustomEqual();
/**
* Create a custom equality comparison method.
*
* This can be done to create very targeted comparisons in extreme hot-path scenarios
* where the standard methods are not performant enough, but can also be used to provide
* support for legacy environments that do not support expected features like
* `RegExp.prototype.flags` out of the box.
* @returns A function that can be used to compare values.
*/
function createCustomEqual() {
const comparator = createEqualityComparator(createEqualityComparatorConfig());
return createIsEqual({
comparator,
equals: createInternalEqualityComparator(comparator)
});
}
//#endregion
export { isEqual as t };
//# sourceMappingURL=isEqual-CjPPCCex.js.map