@tldraw/utils
Version:
tldraw infinite canvas SDK (private utilities).
8 lines (7 loc) • 11.3 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/lib/array.ts"],
"sourcesContent": ["/**\n * Rotate the contents of an array by a specified offset.\n *\n * Creates a new array with elements shifted to the left by the specified number of positions.\n * Both positive and negative offsets result in left shifts (elements move left, with elements\n * from the front wrapping to the back).\n *\n * @param arr - The array to rotate\n * @param offset - The number of positions to shift left (both positive and negative values shift left)\n * @returns A new array with elements shifted left by the specified offset\n *\n * @example\n * ```ts\n * rotateArray([1, 2, 3, 4], 1) // [2, 3, 4, 1]\n * rotateArray([1, 2, 3, 4], -1) // [2, 3, 4, 1]\n * rotateArray(['a', 'b', 'c'], 2) // ['c', 'a', 'b']\n * ```\n * @public\n */\nexport function rotateArray<T>(arr: T[], offset: number): T[] {\n\tif (arr.length === 0) return []\n\n\t// Based on the test expectations, both positive and negative offsets\n\t// should rotate left (shift elements to the left)\n\tconst normalizedOffset = ((Math.abs(offset) % arr.length) + arr.length) % arr.length\n\n\t// Slice the array at the offset point and concatenate\n\treturn [...arr.slice(normalizedOffset), ...arr.slice(0, normalizedOffset)]\n}\n\n/**\n * Remove duplicate items from an array.\n *\n * Creates a new array with duplicate items removed. Uses strict equality by default,\n * or a custom equality function if provided. Order of first occurrence is preserved.\n *\n * @param input - The array to deduplicate\n * @param equals - Optional custom equality function to compare items (defaults to strict equality)\n * @returns A new array with duplicate items removed\n *\n * @example\n * ```ts\n * dedupe([1, 2, 2, 3, 1]) // [1, 2, 3]\n * dedupe(['a', 'b', 'a', 'c']) // ['a', 'b', 'c']\n *\n * // With custom equality function\n * const objects = [{id: 1}, {id: 2}, {id: 1}]\n * dedupe(objects, (a, b) => a.id === b.id) // [{id: 1}, {id: 2}]\n * ```\n * @public\n */\nexport function dedupe<T>(input: T[], equals?: (a: any, b: any) => boolean): T[] {\n\tconst result: T[] = []\n\tmainLoop: for (const item of input) {\n\t\tfor (const existing of result) {\n\t\t\tif (equals ? equals(item, existing) : item === existing) {\n\t\t\t\tcontinue mainLoop\n\t\t\t}\n\t\t}\n\t\tresult.push(item)\n\t}\n\treturn result\n}\n\n/**\n * Remove null and undefined values from an array.\n *\n * Creates a new array with all null and undefined values filtered out.\n * The resulting array has a refined type that excludes null and undefined.\n *\n * @param arr - The array to compact\n * @returns A new array with null and undefined values removed\n *\n * @example\n * ```ts\n * compact([1, null, 2, undefined, 3]) // [1, 2, 3]\n * compact(['a', null, 'b', undefined]) // ['a', 'b']\n * ```\n * @internal\n */\nexport function compact<T>(arr: T[]): NonNullable<T>[] {\n\treturn arr.filter((i) => i !== undefined && i !== null) as any\n}\n\n/**\n * Get the last element of an array.\n *\n * Returns the last element of an array, or undefined if the array is empty.\n * Works with readonly arrays and preserves the element type.\n *\n * @param arr - The array to get the last element from\n * @returns The last element of the array, or undefined if the array is empty\n *\n * @example\n * ```ts\n * last([1, 2, 3]) // 3\n * last(['a', 'b', 'c']) // 'c'\n * last([]) // undefined\n * ```\n * @internal\n */\nexport function last<T>(arr: readonly T[]): T | undefined {\n\treturn arr[arr.length - 1]\n}\n\n/**\n * Find the item in an array with the minimum value according to a function.\n *\n * Finds the array item that produces the smallest value when passed through\n * the provided function. Returns undefined for empty arrays.\n *\n * @param arr - The array to search\n * @param fn - Function to compute the comparison value for each item\n * @returns The item with the minimum value, or undefined if the array is empty\n *\n * @example\n * ```ts\n * const people = [{name: 'Alice', age: 30}, {name: 'Bob', age: 25}]\n * minBy(people, p => p.age) // {name: 'Bob', age: 25}\n *\n * minBy([3, 1, 4, 1, 5], x => x) // 1\n * minBy([], x => x) // undefined\n * ```\n * @internal\n */\nexport function minBy<T>(arr: readonly T[], fn: (item: T) => number): T | undefined {\n\tlet min: T | undefined\n\tlet minVal = Infinity\n\tfor (const item of arr) {\n\t\tconst val = fn(item)\n\t\tif (val < minVal) {\n\t\t\tmin = item\n\t\t\tminVal = val\n\t\t}\n\t}\n\treturn min\n}\n\n/**\n * Find the item in an array with the maximum value according to a function.\n *\n * Finds the array item that produces the largest value when passed through\n * the provided function. Returns undefined for empty arrays.\n *\n * @param arr - The array to search\n * @param fn - Function to compute the comparison value for each item\n * @returns The item with the maximum value, or undefined if the array is empty\n *\n * @example\n * ```ts\n * const people = [{name: 'Alice', age: 30}, {name: 'Bob', age: 25}]\n * maxBy(people, p => p.age) // {name: 'Alice', age: 30}\n *\n * maxBy([3, 1, 4, 1, 5], x => x) // 5\n * maxBy([], x => x) // undefined\n * ```\n * @internal\n */\nexport function maxBy<T>(arr: readonly T[], fn: (item: T) => number): T | undefined {\n\tlet max: T | undefined\n\tlet maxVal: number = -Infinity\n\tfor (const item of arr) {\n\t\tconst val = fn(item)\n\t\tif (val > maxVal) {\n\t\t\tmax = item\n\t\t\tmaxVal = val\n\t\t}\n\t}\n\treturn max\n}\n\n/**\n * Split an array into two arrays based on a predicate function.\n *\n * Partitions an array into two arrays: one containing items that satisfy\n * the predicate, and another containing items that do not. The original array order is preserved.\n *\n * @param arr - The array to partition\n * @param predicate - The predicate function to test each item\n * @returns A tuple of two arrays: [satisfying items, non-satisfying items]\n *\n * @example\n * ```ts\n * const [evens, odds] = partition([1, 2, 3, 4, 5], x => x % 2 === 0)\n * // evens: [2, 4], odds: [1, 3, 5]\n *\n * const [adults, minors] = partition(\n * [{name: 'Alice', age: 30}, {name: 'Bob', age: 17}],\n * person => person.age >= 18\n * )\n * // adults: [{name: 'Alice', age: 30}], minors: [{name: 'Bob', age: 17}]\n * ```\n * @internal\n */\nexport function partition<T>(arr: T[], predicate: (item: T) => boolean): [T[], T[]] {\n\tconst satisfies: T[] = []\n\tconst doesNotSatisfy: T[] = []\n\tfor (const item of arr) {\n\t\tif (predicate(item)) {\n\t\t\tsatisfies.push(item)\n\t\t} else {\n\t\t\tdoesNotSatisfy.push(item)\n\t\t}\n\t}\n\treturn [satisfies, doesNotSatisfy]\n}\n\n/**\n * Check if two arrays are shallow equal.\n *\n * Compares two arrays for shallow equality by checking if they have the same length\n * and the same elements at each index using Object.is comparison. Returns true if arrays are\n * the same reference, have different lengths, or any elements differ.\n *\n * @param arr1 - First array to compare\n * @param arr2 - Second array to compare\n * @returns True if arrays are shallow equal, false otherwise\n *\n * @example\n * ```ts\n * areArraysShallowEqual([1, 2, 3], [1, 2, 3]) // true\n * areArraysShallowEqual([1, 2, 3], [1, 2, 4]) // false\n * areArraysShallowEqual(['a', 'b'], ['a', 'b']) // true\n * areArraysShallowEqual([1, 2], [1, 2, 3]) // false\n *\n * const obj = {x: 1}\n * areArraysShallowEqual([obj], [obj]) // true (same reference)\n * areArraysShallowEqual([{x: 1}], [{x: 1}]) // false (different objects)\n * ```\n * @internal\n */\nexport function areArraysShallowEqual<T>(arr1: readonly T[], arr2: readonly T[]): boolean {\n\tif (arr1 === arr2) return true\n\tif (arr1.length !== arr2.length) return false\n\tfor (let i = 0; i < arr1.length; i++) {\n\t\tif (!Object.is(arr1[i], arr2[i])) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n/**\n * Merge custom entries with defaults, replacing defaults that have matching keys.\n *\n * Combines two arrays by keeping all custom entries and only the default entries\n * that don't have a matching key in the custom entries. Custom entries always override defaults.\n * The result contains remaining defaults first, followed by all custom entries.\n *\n * @param key - The property name to use as the unique identifier\n * @param customEntries - Array of custom entries that will override defaults\n * @param defaults - Array of default entries\n * @returns A new array with defaults filtered out where custom entries exist, plus all custom entries\n *\n * @example\n * ```ts\n * const defaults = [{type: 'text', value: 'default'}, {type: 'number', value: 0}]\n * const custom = [{type: 'text', value: 'custom'}]\n *\n * mergeArraysAndReplaceDefaults('type', custom, defaults)\n * // Result: [{type: 'number', value: 0}, {type: 'text', value: 'custom'}]\n *\n * const tools = [{id: 'select', name: 'Select'}, {id: 'draw', name: 'Draw'}]\n * const customTools = [{id: 'select', name: 'Custom Select'}]\n *\n * mergeArraysAndReplaceDefaults('id', customTools, tools)\n * // Result: [{id: 'draw', name: 'Draw'}, {id: 'select', name: 'Custom Select'}]\n * ```\n * @internal\n */\nexport function mergeArraysAndReplaceDefaults<\n\tconst Key extends string,\n\tT extends { [K in Key]: string },\n>(key: Key, customEntries: readonly T[], defaults: readonly T[]) {\n\tconst overrideTypes = new Set(customEntries.map((entry) => entry[key]))\n\n\tconst result = []\n\tfor (const defaultEntry of defaults) {\n\t\tif (overrideTypes.has(defaultEntry[key])) continue\n\t\tresult.push(defaultEntry)\n\t}\n\n\tfor (const customEntry of customEntries) {\n\t\tresult.push(customEntry)\n\t}\n\n\treturn result\n}\n"],
"mappings": "AAmBO,SAAS,YAAe,KAAU,QAAqB;AAC7D,MAAI,IAAI,WAAW,EAAG,QAAO,CAAC;AAI9B,QAAM,oBAAqB,KAAK,IAAI,MAAM,IAAI,IAAI,SAAU,IAAI,UAAU,IAAI;AAG9E,SAAO,CAAC,GAAG,IAAI,MAAM,gBAAgB,GAAG,GAAG,IAAI,MAAM,GAAG,gBAAgB,CAAC;AAC1E;AAuBO,SAAS,OAAU,OAAY,QAA2C;AAChF,QAAM,SAAc,CAAC;AACrB,WAAU,YAAW,QAAQ,OAAO;AACnC,eAAW,YAAY,QAAQ;AAC9B,UAAI,SAAS,OAAO,MAAM,QAAQ,IAAI,SAAS,UAAU;AACxD,iBAAS;AAAA,MACV;AAAA,IACD;AACA,WAAO,KAAK,IAAI;AAAA,EACjB;AACA,SAAO;AACR;AAkBO,SAAS,QAAW,KAA4B;AACtD,SAAO,IAAI,OAAO,CAAC,MAAM,MAAM,UAAa,MAAM,IAAI;AACvD;AAmBO,SAAS,KAAQ,KAAkC;AACzD,SAAO,IAAI,IAAI,SAAS,CAAC;AAC1B;AAsBO,SAAS,MAAS,KAAmB,IAAwC;AACnF,MAAI;AACJ,MAAI,SAAS;AACb,aAAW,QAAQ,KAAK;AACvB,UAAM,MAAM,GAAG,IAAI;AACnB,QAAI,MAAM,QAAQ;AACjB,YAAM;AACN,eAAS;AAAA,IACV;AAAA,EACD;AACA,SAAO;AACR;AAsBO,SAAS,MAAS,KAAmB,IAAwC;AACnF,MAAI;AACJ,MAAI,SAAiB;AACrB,aAAW,QAAQ,KAAK;AACvB,UAAM,MAAM,GAAG,IAAI;AACnB,QAAI,MAAM,QAAQ;AACjB,YAAM;AACN,eAAS;AAAA,IACV;AAAA,EACD;AACA,SAAO;AACR;AAyBO,SAAS,UAAa,KAAU,WAA6C;AACnF,QAAM,YAAiB,CAAC;AACxB,QAAM,iBAAsB,CAAC;AAC7B,aAAW,QAAQ,KAAK;AACvB,QAAI,UAAU,IAAI,GAAG;AACpB,gBAAU,KAAK,IAAI;AAAA,IACpB,OAAO;AACN,qBAAe,KAAK,IAAI;AAAA,IACzB;AAAA,EACD;AACA,SAAO,CAAC,WAAW,cAAc;AAClC;AA0BO,SAAS,sBAAyB,MAAoB,MAA6B;AACzF,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACrC,QAAI,CAAC,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG;AACjC,aAAO;AAAA,IACR;AAAA,EACD;AACA,SAAO;AACR;AA8BO,SAAS,8BAGd,KAAU,eAA6B,UAAwB;AAChE,QAAM,gBAAgB,IAAI,IAAI,cAAc,IAAI,CAAC,UAAU,MAAM,GAAG,CAAC,CAAC;AAEtE,QAAM,SAAS,CAAC;AAChB,aAAW,gBAAgB,UAAU;AACpC,QAAI,cAAc,IAAI,aAAa,GAAG,CAAC,EAAG;AAC1C,WAAO,KAAK,YAAY;AAAA,EACzB;AAEA,aAAW,eAAe,eAAe;AACxC,WAAO,KAAK,WAAW;AAAA,EACxB;AAEA,SAAO;AACR;",
"names": []
}