UNPKG

typedash

Version:

modern, type-safe collection of utility functions

398 lines (377 loc) 15.3 kB
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