@ocavue/utils 1.0.0 → 1.1.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/dist/index.d.ts +23 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +98 -38
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/default-map.test.ts +7 -4
- package/src/index.ts +3 -1
- package/src/is-deep-equal.test.ts +31 -27
- package/src/map-group-by.test.ts +78 -0
- package/src/map-group-by.ts +41 -0
- package/src/object-group-by.test.ts +86 -0
- package/src/object-group-by.ts +43 -0
package/dist/index.d.ts
CHANGED
|
@@ -98,6 +98,28 @@ declare function formatBytes(bytes: number): string;
|
|
|
98
98
|
*/
|
|
99
99
|
declare function getId(): number;
|
|
100
100
|
//#endregion
|
|
101
|
+
//#region src/is-deep-equal.d.ts
|
|
102
|
+
/**
|
|
103
|
+
* Whether two values are deeply equal.
|
|
104
|
+
*/
|
|
105
|
+
declare function isDeepEqual(a: unknown, b: unknown): boolean;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/map-group-by.d.ts
|
|
108
|
+
/**
|
|
109
|
+
* A polyfill for the `Map.groupBy` static method.
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
declare const mapGroupBy: <K, T>(items: Iterable<T>, keySelector: (item: T, index: number) => K) => Map<K, T[]>;
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/object-group-by.d.ts
|
|
116
|
+
/**
|
|
117
|
+
* A polyfill for the `Object.groupBy` static method.
|
|
118
|
+
*
|
|
119
|
+
* @public
|
|
120
|
+
*/
|
|
121
|
+
declare const objectGroupBy: <K extends PropertyKey, T>(items: Iterable<T>, keySelector: (item: T, index: number) => K) => Partial<Record<K, T[]>>;
|
|
122
|
+
//#endregion
|
|
101
123
|
//#region src/once.d.ts
|
|
102
124
|
/**
|
|
103
125
|
* Creates a function that will only execute the provided function once.
|
|
@@ -120,11 +142,5 @@ declare function once<T>(fn: () => T): () => T;
|
|
|
120
142
|
*/
|
|
121
143
|
declare function sleep(ms: number): Promise<void>;
|
|
122
144
|
//#endregion
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Whether two values are deeply equal.
|
|
126
|
-
*/
|
|
127
|
-
declare function isDeepEqual(a: unknown, b: unknown): boolean;
|
|
128
|
-
//#endregion
|
|
129
|
-
export { DefaultMap, formatBytes, getDocument, getDocumentElement, getId, getWindow, isDeepEqual, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMap, isMathMLElement, isNodeLike, isObject, isSVGElement, isSet, isShadowRoot, isTextNode, isWindowLike, once, sleep };
|
|
145
|
+
export { DefaultMap, formatBytes, getDocument, getDocumentElement, getId, getWindow, isDeepEqual, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMap, isMathMLElement, isNodeLike, isObject, isSVGElement, isSet, isShadowRoot, isTextNode, isWindowLike, mapGroupBy, objectGroupBy, once, sleep };
|
|
130
146
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/is-deep-equal.ts","../src/map-group-by.ts","../src/object-group-by.ts","../src/once.ts","../src/sleep.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -179,43 +179,6 @@ function getId() {
|
|
|
179
179
|
return id;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
//#endregion
|
|
183
|
-
//#region src/once.ts
|
|
184
|
-
/**
|
|
185
|
-
* Creates a function that will only execute the provided function once.
|
|
186
|
-
* Subsequent calls will return the cached result from the first execution.
|
|
187
|
-
*
|
|
188
|
-
* @param fn The function to execute once
|
|
189
|
-
* @returns A function that will only execute the provided function once
|
|
190
|
-
* @example
|
|
191
|
-
* ```ts
|
|
192
|
-
* const getValue = once(() => expensiveOperation())
|
|
193
|
-
* getValue() // executes expensiveOperation
|
|
194
|
-
* getValue() // returns cached result
|
|
195
|
-
* ```
|
|
196
|
-
*/
|
|
197
|
-
function once(fn) {
|
|
198
|
-
let called = false;
|
|
199
|
-
let result;
|
|
200
|
-
return () => {
|
|
201
|
-
if (!called) {
|
|
202
|
-
result = fn();
|
|
203
|
-
called = true;
|
|
204
|
-
fn = void 0;
|
|
205
|
-
}
|
|
206
|
-
return result;
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
//#endregion
|
|
211
|
-
//#region src/sleep.ts
|
|
212
|
-
/**
|
|
213
|
-
* Sleep for a given number of milliseconds.
|
|
214
|
-
*/
|
|
215
|
-
function sleep(ms) {
|
|
216
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
182
|
//#endregion
|
|
220
183
|
//#region src/is-deep-equal.ts
|
|
221
184
|
/**
|
|
@@ -260,5 +223,102 @@ function isDeepEqual(a, b) {
|
|
|
260
223
|
}
|
|
261
224
|
|
|
262
225
|
//#endregion
|
|
263
|
-
|
|
226
|
+
//#region src/map-group-by.ts
|
|
227
|
+
/**
|
|
228
|
+
* @internal
|
|
229
|
+
*/
|
|
230
|
+
function mapGroupByPolyfill(items, keySelector) {
|
|
231
|
+
const map = /* @__PURE__ */ new Map();
|
|
232
|
+
let index = 0;
|
|
233
|
+
for (const item of items) {
|
|
234
|
+
const key = keySelector(item, index);
|
|
235
|
+
const group = map.get(key);
|
|
236
|
+
if (group) group.push(item);
|
|
237
|
+
else map.set(key, [item]);
|
|
238
|
+
index++;
|
|
239
|
+
}
|
|
240
|
+
return map;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* @internal
|
|
244
|
+
*/
|
|
245
|
+
function mapGroupByNative(items, keySelector) {
|
|
246
|
+
return Map.groupBy(items, keySelector);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* A polyfill for the `Map.groupBy` static method.
|
|
250
|
+
*
|
|
251
|
+
* @public
|
|
252
|
+
*/
|
|
253
|
+
const mapGroupBy = !!Map.groupBy ? mapGroupByNative : mapGroupByPolyfill;
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/object-group-by.ts
|
|
257
|
+
/**
|
|
258
|
+
* @internal
|
|
259
|
+
*/
|
|
260
|
+
function objectGroupByPolyfill(items, keySelector) {
|
|
261
|
+
const result = {};
|
|
262
|
+
let index = 0;
|
|
263
|
+
for (const item of items) {
|
|
264
|
+
const key = keySelector(item, index);
|
|
265
|
+
const group = result[key];
|
|
266
|
+
if (group) group.push(item);
|
|
267
|
+
else result[key] = [item];
|
|
268
|
+
index++;
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* @internal
|
|
274
|
+
*/
|
|
275
|
+
function objectGroupByNative(items, keySelector) {
|
|
276
|
+
return Object.groupBy(items, keySelector);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* A polyfill for the `Object.groupBy` static method.
|
|
280
|
+
*
|
|
281
|
+
* @public
|
|
282
|
+
*/
|
|
283
|
+
const objectGroupBy = !!Object.groupBy ? objectGroupByNative : objectGroupByPolyfill;
|
|
284
|
+
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/once.ts
|
|
287
|
+
/**
|
|
288
|
+
* Creates a function that will only execute the provided function once.
|
|
289
|
+
* Subsequent calls will return the cached result from the first execution.
|
|
290
|
+
*
|
|
291
|
+
* @param fn The function to execute once
|
|
292
|
+
* @returns A function that will only execute the provided function once
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* const getValue = once(() => expensiveOperation())
|
|
296
|
+
* getValue() // executes expensiveOperation
|
|
297
|
+
* getValue() // returns cached result
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
function once(fn) {
|
|
301
|
+
let called = false;
|
|
302
|
+
let result;
|
|
303
|
+
return () => {
|
|
304
|
+
if (!called) {
|
|
305
|
+
result = fn();
|
|
306
|
+
called = true;
|
|
307
|
+
fn = void 0;
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/sleep.ts
|
|
315
|
+
/**
|
|
316
|
+
* Sleep for a given number of milliseconds.
|
|
317
|
+
*/
|
|
318
|
+
function sleep(ms) {
|
|
319
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
//#endregion
|
|
323
|
+
export { DefaultMap, formatBytes, getDocument, getDocumentElement, getId, getWindow, isDeepEqual, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMap, isMathMLElement, isNodeLike, isObject, isSVGElement, isSet, isShadowRoot, isTextNode, isWindowLike, mapGroupBy, objectGroupBy, once, sleep };
|
|
264
324
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["NodeType.ELEMENT_NODE","NodeType.TEXT_NODE","NodeType.DOCUMENT_NODE","NodeType.DOCUMENT_FRAGMENT_NODE","result: T"],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom-node-type.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/once.ts","../src/sleep.ts","../src/is-deep-equal.ts"],"sourcesContent":["/**\n * Checks if the given value is an object.\n */\nexport function isObject(\n value: unknown,\n): value is Record<string | symbol | number, unknown> {\n return value != null && typeof value === 'object'\n}\n\n/**\n * Checks if the given value is a Map.\n */\nexport function isMap(value: unknown): value is Map<unknown, unknown> {\n return value instanceof Map\n}\n\n/**\n * Checks if the given value is a Set.\n */\nexport function isSet(value: unknown): value is Set<unknown> {\n return value instanceof Set\n}\n","/**\n * A map that automatically creates values for missing keys using a factory function.\n *\n * Similar to Python's [defaultdict](https://docs.python.org/3.13/library/collections.html#collections.defaultdict).\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n private readonly defaultFactory: () => V\n\n constructor(defaultFactory: () => V, iterable?: Iterable<readonly [K, V]>) {\n super(iterable)\n this.defaultFactory = defaultFactory\n }\n\n override get(key: K): V {\n if (this.has(key)) {\n return super.get(key)!\n }\n const value = this.defaultFactory()\n this.set(key, value)\n return value\n }\n}\n","// prettier-ignore\nexport const ELEMENT_NODE = 1 satisfies typeof Node.ELEMENT_NODE;\n// prettier-ignore\nexport const ATTRIBUTE_NODE = 2 satisfies typeof Node.ATTRIBUTE_NODE;\n// prettier-ignore\nexport const TEXT_NODE = 3 satisfies typeof Node.TEXT_NODE;\n// prettier-ignore\nexport const CDATA_SECTION_NODE = 4 satisfies typeof Node.CDATA_SECTION_NODE;\n// prettier-ignore\nexport const ENTITY_REFERENCE_NODE = 5 satisfies typeof Node.ENTITY_REFERENCE_NODE;\n// prettier-ignore\nexport const ENTITY_NODE = 6 satisfies typeof Node.ENTITY_NODE;\n// prettier-ignore\nexport const PROCESSING_INSTRUCTION_NODE = 7 satisfies typeof Node.PROCESSING_INSTRUCTION_NODE;\n// prettier-ignore\nexport const COMMENT_NODE = 8 satisfies typeof Node.COMMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_NODE = 9 satisfies typeof Node.DOCUMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_TYPE_NODE = 10 satisfies typeof Node.DOCUMENT_TYPE_NODE;\n// prettier-ignore\nexport const DOCUMENT_FRAGMENT_NODE = 11 satisfies typeof Node.DOCUMENT_FRAGMENT_NODE;\n// prettier-ignore\nexport const NOTATION_NODE = 12 satisfies typeof Node.NOTATION_NODE;\n","import { isObject } from './checker'\nimport * as NodeType from './dom-node-type'\n\n/**\n * Checks if the given DOM node is an Element.\n */\nexport function isElement(node: Node): node is Element {\n return node.nodeType === NodeType.ELEMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node.nodeType === NodeType.TEXT_NODE\n}\n\n/**\n * Checks if the given DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/1999/xhtml'\n}\n\n/**\n * Checks if the given DOM node is an SVGElement.\n */\nexport function isSVGElement(node: Node): node is SVGElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/2000/svg'\n}\n\n/**\n * Checks if the given DOM node is an MathMLElement.\n */\nexport function isMathMLElement(node: Node): node is MathMLElement {\n return (\n isElement(node) &&\n node.namespaceURI === 'http://www.w3.org/1998/Math/MathML'\n )\n}\n\n/**\n * Checks if the given DOM node is a Document.\n */\nexport function isDocument(node: Node): node is Document {\n return node.nodeType === NodeType.DOCUMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a DocumentFragment.\n */\nexport function isDocumentFragment(node: Node): node is DocumentFragment {\n return node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a ShadowRoot.\n */\nexport function isShadowRoot(node: Node): node is ShadowRoot {\n return isDocumentFragment(node) && 'host' in node && isElementLike(node.host)\n}\n\n/**\n * Checks if an unknown value is likely a DOM node.\n */\nexport function isNodeLike(value: unknown): value is Node {\n return isObject(value) && value.nodeType !== undefined\n}\n\n/**\n * Checks if an unknown value is likely a DOM element.\n */\nexport function isElementLike(value: unknown): value is Element {\n return (\n isObject(value) &&\n value.nodeType === NodeType.ELEMENT_NODE &&\n typeof value.nodeName === 'string'\n )\n}\n\n/**\n * Checks if the given value is likely a Window object.\n */\nexport function isWindowLike(value: unknown): value is Window {\n return isObject(value) && value.window === value\n}\n\n/**\n * Gets the window object for the given target or the global window object if no\n * target is provided.\n */\nexport function getWindow(\n target?: Node | ShadowRoot | Document | null,\n): Window & typeof globalThis {\n if (target) {\n if (isShadowRoot(target)) {\n return getWindow(target.host)\n }\n if (isDocument(target)) {\n return target.defaultView || window\n }\n if (isElement(target)) {\n return target.ownerDocument?.defaultView || window\n }\n }\n return window\n}\n\n/**\n * Gets the document for the given target or the global document if no target is\n * provided.\n */\nexport function getDocument(\n target?: Element | Window | Node | Document | null,\n): Document {\n if (target) {\n if (isWindowLike(target)) {\n return target.document\n }\n if (isDocument(target)) {\n return target\n }\n return target.ownerDocument || document\n }\n return document\n}\n\n/**\n * Gets a reference to the root node of the document based on the given target.\n */\nexport function getDocumentElement(\n target?: Element | Node | Window | Document | null,\n): HTMLElement {\n return getDocument(target).documentElement\n}\n","/**\n * Formats a number of bytes into a human-readable string.\n * @param bytes - The number of bytes to format.\n * @returns A string representing the number of bytes in a human-readable format.\n */\nexport function formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB']\n let unitIndex = 0\n let num = bytes\n while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {\n num /= 1024\n unitIndex++\n }\n const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1\n return `${num.toFixed(fraction)}${units[unitIndex]}`\n}\n","let id = 0\n\n/**\n * Generates a unique positive integer.\n */\nexport function getId(): number {\n id = (id % Number.MAX_SAFE_INTEGER) + 1\n return id\n}\n","/**\n * Creates a function that will only execute the provided function once.\n * Subsequent calls will return the cached result from the first execution.\n *\n * @param fn The function to execute once\n * @returns A function that will only execute the provided function once\n * @example\n * ```ts\n * const getValue = once(() => expensiveOperation())\n * getValue() // executes expensiveOperation\n * getValue() // returns cached result\n * ```\n */\nexport function once<T>(fn: () => T): () => T {\n let called = false\n let result: T\n return () => {\n if (!called) {\n result = fn()\n called = true\n // @ts-expect-error - micro-optimization for memory management\n fn = undefined\n }\n return result\n }\n}\n","/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n","import { isMap, isSet } from './checker'\n\n/**\n * Whether two values are deeply equal.\n */\nexport function isDeepEqual(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true\n }\n\n // Handle null and undefined early\n if (a == null || b == null) {\n return false\n }\n\n const aType = typeof a\n const bType = typeof b\n if (aType !== bType) {\n return false\n }\n\n if (aType === 'number' && Number.isNaN(a) && Number.isNaN(b)) {\n return true\n }\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) {\n return false\n }\n if (a.length !== b.length) {\n return false\n }\n const size = a.length\n for (let i = 0; i < size; i++) {\n if (!isDeepEqual(a[i], b[i])) {\n return false\n }\n }\n return true\n }\n\n if (isSet(a)) {\n if (!isSet(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const value of a) {\n if (!b.has(value)) {\n return false\n }\n }\n return true\n }\n\n if (isMap(a)) {\n if (!isMap(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const key of a.keys()) {\n if (!b.has(key)) {\n return false\n }\n if (!isDeepEqual(a.get(key), b.get(key))) {\n return false\n }\n }\n return true\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) {\n return false\n }\n for (const key of aKeys) {\n if (\n !isDeepEqual(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false\n }\n }\n return true\n }\n\n return false\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["NodeType.ELEMENT_NODE","NodeType.TEXT_NODE","NodeType.DOCUMENT_NODE","NodeType.DOCUMENT_FRAGMENT_NODE","mapGroupBy: <K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Map<K, T[]>","result: Partial<Record<K, T[]>>","objectGroupBy: <K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Partial<Record<K, T[]>>","result: T"],"sources":["../src/checker.ts","../src/default-map.ts","../src/dom-node-type.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/is-deep-equal.ts","../src/map-group-by.ts","../src/object-group-by.ts","../src/once.ts","../src/sleep.ts"],"sourcesContent":["/**\n * Checks if the given value is an object.\n */\nexport function isObject(\n value: unknown,\n): value is Record<string | symbol | number, unknown> {\n return value != null && typeof value === 'object'\n}\n\n/**\n * Checks if the given value is a Map.\n */\nexport function isMap(value: unknown): value is Map<unknown, unknown> {\n return value instanceof Map\n}\n\n/**\n * Checks if the given value is a Set.\n */\nexport function isSet(value: unknown): value is Set<unknown> {\n return value instanceof Set\n}\n","/**\n * A map that automatically creates values for missing keys using a factory function.\n *\n * Similar to Python's [defaultdict](https://docs.python.org/3.13/library/collections.html#collections.defaultdict).\n */\nexport class DefaultMap<K, V> extends Map<K, V> {\n private readonly defaultFactory: () => V\n\n constructor(defaultFactory: () => V, iterable?: Iterable<readonly [K, V]>) {\n super(iterable)\n this.defaultFactory = defaultFactory\n }\n\n override get(key: K): V {\n if (this.has(key)) {\n return super.get(key)!\n }\n const value = this.defaultFactory()\n this.set(key, value)\n return value\n }\n}\n","// prettier-ignore\nexport const ELEMENT_NODE = 1 satisfies typeof Node.ELEMENT_NODE;\n// prettier-ignore\nexport const ATTRIBUTE_NODE = 2 satisfies typeof Node.ATTRIBUTE_NODE;\n// prettier-ignore\nexport const TEXT_NODE = 3 satisfies typeof Node.TEXT_NODE;\n// prettier-ignore\nexport const CDATA_SECTION_NODE = 4 satisfies typeof Node.CDATA_SECTION_NODE;\n// prettier-ignore\nexport const ENTITY_REFERENCE_NODE = 5 satisfies typeof Node.ENTITY_REFERENCE_NODE;\n// prettier-ignore\nexport const ENTITY_NODE = 6 satisfies typeof Node.ENTITY_NODE;\n// prettier-ignore\nexport const PROCESSING_INSTRUCTION_NODE = 7 satisfies typeof Node.PROCESSING_INSTRUCTION_NODE;\n// prettier-ignore\nexport const COMMENT_NODE = 8 satisfies typeof Node.COMMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_NODE = 9 satisfies typeof Node.DOCUMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_TYPE_NODE = 10 satisfies typeof Node.DOCUMENT_TYPE_NODE;\n// prettier-ignore\nexport const DOCUMENT_FRAGMENT_NODE = 11 satisfies typeof Node.DOCUMENT_FRAGMENT_NODE;\n// prettier-ignore\nexport const NOTATION_NODE = 12 satisfies typeof Node.NOTATION_NODE;\n","import { isObject } from './checker'\nimport * as NodeType from './dom-node-type'\n\n/**\n * Checks if the given DOM node is an Element.\n */\nexport function isElement(node: Node): node is Element {\n return node.nodeType === NodeType.ELEMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node.nodeType === NodeType.TEXT_NODE\n}\n\n/**\n * Checks if the given DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/1999/xhtml'\n}\n\n/**\n * Checks if the given DOM node is an SVGElement.\n */\nexport function isSVGElement(node: Node): node is SVGElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/2000/svg'\n}\n\n/**\n * Checks if the given DOM node is an MathMLElement.\n */\nexport function isMathMLElement(node: Node): node is MathMLElement {\n return (\n isElement(node) &&\n node.namespaceURI === 'http://www.w3.org/1998/Math/MathML'\n )\n}\n\n/**\n * Checks if the given DOM node is a Document.\n */\nexport function isDocument(node: Node): node is Document {\n return node.nodeType === NodeType.DOCUMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a DocumentFragment.\n */\nexport function isDocumentFragment(node: Node): node is DocumentFragment {\n return node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a ShadowRoot.\n */\nexport function isShadowRoot(node: Node): node is ShadowRoot {\n return isDocumentFragment(node) && 'host' in node && isElementLike(node.host)\n}\n\n/**\n * Checks if an unknown value is likely a DOM node.\n */\nexport function isNodeLike(value: unknown): value is Node {\n return isObject(value) && value.nodeType !== undefined\n}\n\n/**\n * Checks if an unknown value is likely a DOM element.\n */\nexport function isElementLike(value: unknown): value is Element {\n return (\n isObject(value) &&\n value.nodeType === NodeType.ELEMENT_NODE &&\n typeof value.nodeName === 'string'\n )\n}\n\n/**\n * Checks if the given value is likely a Window object.\n */\nexport function isWindowLike(value: unknown): value is Window {\n return isObject(value) && value.window === value\n}\n\n/**\n * Gets the window object for the given target or the global window object if no\n * target is provided.\n */\nexport function getWindow(\n target?: Node | ShadowRoot | Document | null,\n): Window & typeof globalThis {\n if (target) {\n if (isShadowRoot(target)) {\n return getWindow(target.host)\n }\n if (isDocument(target)) {\n return target.defaultView || window\n }\n if (isElement(target)) {\n return target.ownerDocument?.defaultView || window\n }\n }\n return window\n}\n\n/**\n * Gets the document for the given target or the global document if no target is\n * provided.\n */\nexport function getDocument(\n target?: Element | Window | Node | Document | null,\n): Document {\n if (target) {\n if (isWindowLike(target)) {\n return target.document\n }\n if (isDocument(target)) {\n return target\n }\n return target.ownerDocument || document\n }\n return document\n}\n\n/**\n * Gets a reference to the root node of the document based on the given target.\n */\nexport function getDocumentElement(\n target?: Element | Node | Window | Document | null,\n): HTMLElement {\n return getDocument(target).documentElement\n}\n","/**\n * Formats a number of bytes into a human-readable string.\n * @param bytes - The number of bytes to format.\n * @returns A string representing the number of bytes in a human-readable format.\n */\nexport function formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB']\n let unitIndex = 0\n let num = bytes\n while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {\n num /= 1024\n unitIndex++\n }\n const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1\n return `${num.toFixed(fraction)}${units[unitIndex]}`\n}\n","let id = 0\n\n/**\n * Generates a unique positive integer.\n */\nexport function getId(): number {\n id = (id % Number.MAX_SAFE_INTEGER) + 1\n return id\n}\n","import { isMap, isSet } from './checker'\n\n/**\n * Whether two values are deeply equal.\n */\nexport function isDeepEqual(a: unknown, b: unknown): boolean {\n if (a === b) {\n return true\n }\n\n // Handle null and undefined early\n if (a == null || b == null) {\n return false\n }\n\n const aType = typeof a\n const bType = typeof b\n if (aType !== bType) {\n return false\n }\n\n if (aType === 'number' && Number.isNaN(a) && Number.isNaN(b)) {\n return true\n }\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) {\n return false\n }\n if (a.length !== b.length) {\n return false\n }\n const size = a.length\n for (let i = 0; i < size; i++) {\n if (!isDeepEqual(a[i], b[i])) {\n return false\n }\n }\n return true\n }\n\n if (isSet(a)) {\n if (!isSet(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const value of a) {\n if (!b.has(value)) {\n return false\n }\n }\n return true\n }\n\n if (isMap(a)) {\n if (!isMap(b)) {\n return false\n }\n if (a.size !== b.size) {\n return false\n }\n for (const key of a.keys()) {\n if (!b.has(key)) {\n return false\n }\n if (!isDeepEqual(a.get(key), b.get(key))) {\n return false\n }\n }\n return true\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) {\n return false\n }\n for (const key of aKeys) {\n if (\n !isDeepEqual(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false\n }\n }\n return true\n }\n\n return false\n}\n","/**\n * @internal\n */\nexport function mapGroupByPolyfill<K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Map<K, T[]> {\n const map = new Map<K, T[]>()\n let index = 0\n for (const item of items) {\n const key = keySelector(item, index)\n const group = map.get(key)\n if (group) {\n group.push(item)\n } else {\n map.set(key, [item])\n }\n index++\n }\n return map\n}\n\n/**\n * @internal\n */\nexport function mapGroupByNative<K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Map<K, T[]> {\n return Map.groupBy(items, keySelector)\n}\n\n/**\n * A polyfill for the `Map.groupBy` static method.\n *\n * @public\n */\nexport const mapGroupBy: <K, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Map<K, T[]> = !!Map.groupBy ? mapGroupByNative : mapGroupByPolyfill\n","/**\n * @internal\n */\nexport function objectGroupByPolyfill<K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Partial<Record<K, T[]>> {\n const result: Partial<Record<K, T[]>> = {}\n let index = 0\n for (const item of items) {\n const key = keySelector(item, index)\n const group = result[key]\n if (group) {\n group.push(item)\n } else {\n result[key] = [item]\n }\n index++\n }\n return result\n}\n\n/**\n * @internal\n */\nexport function objectGroupByNative<K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n): Partial<Record<K, T[]>> {\n return Object.groupBy(items, keySelector)\n}\n\n/**\n * A polyfill for the `Object.groupBy` static method.\n *\n * @public\n */\nexport const objectGroupBy: <K extends PropertyKey, T>(\n items: Iterable<T>,\n keySelector: (item: T, index: number) => K,\n) => Partial<Record<K, T[]>> = !!Object.groupBy\n ? objectGroupByNative\n : objectGroupByPolyfill\n","/**\n * Creates a function that will only execute the provided function once.\n * Subsequent calls will return the cached result from the first execution.\n *\n * @param fn The function to execute once\n * @returns A function that will only execute the provided function once\n * @example\n * ```ts\n * const getValue = once(() => expensiveOperation())\n * getValue() // executes expensiveOperation\n * getValue() // returns cached result\n * ```\n */\nexport function once<T>(fn: () => T): () => T {\n let called = false\n let result: T\n return () => {\n if (!called) {\n result = fn()\n called = true\n // @ts-expect-error - micro-optimization for memory management\n fn = undefined\n }\n return result\n }\n}\n","/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],"mappings":";;;;AAGA,SAAgB,SACd,OACoD;AACpD,QAAO,SAAS,QAAQ,OAAO,UAAU;;;;;AAM3C,SAAgB,MAAM,OAAgD;AACpE,QAAO,iBAAiB;;;;;AAM1B,SAAgB,MAAM,OAAuC;AAC3D,QAAO,iBAAiB;;;;;;;;;;ACf1B,IAAa,aAAb,cAAsC,IAAU;CAG9C,YAAY,gBAAyB,UAAsC;AACzE,QAAM,SAAS;AACf,OAAK,iBAAiB;;CAGxB,AAAS,IAAI,KAAW;AACtB,MAAI,KAAK,IAAI,IAAI,CACf,QAAO,MAAM,IAAI,IAAI;EAEvB,MAAM,QAAQ,KAAK,gBAAgB;AACnC,OAAK,IAAI,KAAK,MAAM;AACpB,SAAO;;;;;;AClBX,MAAa,eAAe;AAI5B,MAAa,YAAY;AAYzB,MAAa,gBAAgB;AAI7B,MAAa,yBAAyB;;;;;;;ACftC,SAAgB,UAAU,MAA6B;AACrD,QAAO,KAAK,aAAaA;;;;;AAM3B,SAAgB,WAAW,MAA0B;AACnD,QAAO,KAAK,aAAaC;;;;;AAM3B,SAAgB,cAAc,MAAiC;AAC7D,QAAO,UAAU,KAAK,IAAI,KAAK,iBAAiB;;;;;AAMlD,SAAgB,aAAa,MAAgC;AAC3D,QAAO,UAAU,KAAK,IAAI,KAAK,iBAAiB;;;;;AAMlD,SAAgB,gBAAgB,MAAmC;AACjE,QACE,UAAU,KAAK,IACf,KAAK,iBAAiB;;;;;AAO1B,SAAgB,WAAW,MAA8B;AACvD,QAAO,KAAK,aAAaC;;;;;AAM3B,SAAgB,mBAAmB,MAAsC;AACvE,QAAO,KAAK,aAAaC;;;;;AAM3B,SAAgB,aAAa,MAAgC;AAC3D,QAAO,mBAAmB,KAAK,IAAI,UAAU,QAAQ,cAAc,KAAK,KAAK;;;;;AAM/E,SAAgB,WAAW,OAA+B;AACxD,QAAO,SAAS,MAAM,IAAI,MAAM,aAAa;;;;;AAM/C,SAAgB,cAAc,OAAkC;AAC9D,QACE,SAAS,MAAM,IACf,MAAM,aAAaH,gBACnB,OAAO,MAAM,aAAa;;;;;AAO9B,SAAgB,aAAa,OAAiC;AAC5D,QAAO,SAAS,MAAM,IAAI,MAAM,WAAW;;;;;;AAO7C,SAAgB,UACd,QAC4B;AAC5B,KAAI,QAAQ;AACV,MAAI,aAAa,OAAO,CACtB,QAAO,UAAU,OAAO,KAAK;AAE/B,MAAI,WAAW,OAAO,CACpB,QAAO,OAAO,eAAe;AAE/B,MAAI,UAAU,OAAO,CACnB,QAAO,OAAO,eAAe,eAAe;;AAGhD,QAAO;;;;;;AAOT,SAAgB,YACd,QACU;AACV,KAAI,QAAQ;AACV,MAAI,aAAa,OAAO,CACtB,QAAO,OAAO;AAEhB,MAAI,WAAW,OAAO,CACpB,QAAO;AAET,SAAO,OAAO,iBAAiB;;AAEjC,QAAO;;;;;AAMT,SAAgB,mBACd,QACa;AACb,QAAO,YAAY,OAAO,CAAC;;;;;;;;;;AChI7B,SAAgB,YAAY,OAAuB;CACjD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAK;CACrC,IAAI,YAAY;CAChB,IAAI,MAAM;AACV,QAAO,KAAK,IAAI,IAAI,IAAI,QAAQ,YAAY,MAAM,SAAS,GAAG;AAC5D,SAAO;AACP;;CAEF,MAAM,WAAW,cAAc,KAAK,MAAM,MAAM,IAAI,IAAI;AACxD,QAAO,GAAG,IAAI,QAAQ,SAAS,GAAG,MAAM;;;;;ACd1C,IAAI,KAAK;;;;AAKT,SAAgB,QAAgB;AAC9B,MAAM,KAAK,OAAO,mBAAoB;AACtC,QAAO;;;;;;;;ACFT,SAAgB,YAAY,GAAY,GAAqB;AAC3D,KAAI,MAAM,EACR,QAAO;AAIT,KAAI,KAAK,QAAQ,KAAK,KACpB,QAAO;CAGT,MAAM,QAAQ,OAAO;AAErB,KAAI,UADU,OAAO,EAEnB,QAAO;AAGT,KAAI,UAAU,YAAY,OAAO,MAAM,EAAE,IAAI,OAAO,MAAM,EAAE,CAC1D,QAAO;AAGT,KAAI,MAAM,QAAQ,EAAE,EAAE;AACpB,MAAI,CAAC,MAAM,QAAQ,EAAE,CACnB,QAAO;AAET,MAAI,EAAE,WAAW,EAAE,OACjB,QAAO;EAET,MAAM,OAAO,EAAE;AACf,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,IACxB,KAAI,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,CAC1B,QAAO;AAGX,SAAO;;AAGT,KAAI,MAAM,EAAE,EAAE;AACZ,MAAI,CAAC,MAAM,EAAE,CACX,QAAO;AAET,MAAI,EAAE,SAAS,EAAE,KACf,QAAO;AAET,OAAK,MAAM,SAAS,EAClB,KAAI,CAAC,EAAE,IAAI,MAAM,CACf,QAAO;AAGX,SAAO;;AAGT,KAAI,MAAM,EAAE,EAAE;AACZ,MAAI,CAAC,MAAM,EAAE,CACX,QAAO;AAET,MAAI,EAAE,SAAS,EAAE,KACf,QAAO;AAET,OAAK,MAAM,OAAO,EAAE,MAAM,EAAE;AAC1B,OAAI,CAAC,EAAE,IAAI,IAAI,CACb,QAAO;AAET,OAAI,CAAC,YAAY,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,IAAI,CAAC,CACtC,QAAO;;AAGX,SAAO;;AAGT,KAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;EAClD,MAAM,QAAQ,OAAO,KAAK,EAAE;EAC5B,MAAM,QAAQ,OAAO,KAAK,EAAE;AAC5B,MAAI,MAAM,WAAW,MAAM,OACzB,QAAO;AAET,OAAK,MAAM,OAAO,MAChB,KACE,CAAC,YACE,EAA8B,MAC9B,EAA8B,KAChC,CAED,QAAO;AAGX,SAAO;;AAGT,QAAO;;;;;;;;AC1FT,SAAgB,mBACd,OACA,aACa;CACb,MAAM,sBAAM,IAAI,KAAa;CAC7B,IAAI,QAAQ;AACZ,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,YAAY,MAAM,MAAM;EACpC,MAAM,QAAQ,IAAI,IAAI,IAAI;AAC1B,MAAI,MACF,OAAM,KAAK,KAAK;MAEhB,KAAI,IAAI,KAAK,CAAC,KAAK,CAAC;AAEtB;;AAEF,QAAO;;;;;AAMT,SAAgB,iBACd,OACA,aACa;AACb,QAAO,IAAI,QAAQ,OAAO,YAAY;;;;;;;AAQxC,MAAaI,aAGM,CAAC,CAAC,IAAI,UAAU,mBAAmB;;;;;;;ACrCtD,SAAgB,sBACd,OACA,aACyB;CACzB,MAAMC,SAAkC,EAAE;CAC1C,IAAI,QAAQ;AACZ,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,YAAY,MAAM,MAAM;EACpC,MAAM,QAAQ,OAAO;AACrB,MAAI,MACF,OAAM,KAAK,KAAK;MAEhB,QAAO,OAAO,CAAC,KAAK;AAEtB;;AAEF,QAAO;;;;;AAMT,SAAgB,oBACd,OACA,aACyB;AACzB,QAAO,OAAO,QAAQ,OAAO,YAAY;;;;;;;AAQ3C,MAAaC,gBAGkB,CAAC,CAAC,OAAO,UACpC,sBACA;;;;;;;;;;;;;;;;;AC7BJ,SAAgB,KAAQ,IAAsB;CAC5C,IAAI,SAAS;CACb,IAAIC;AACJ,cAAa;AACX,MAAI,CAAC,QAAQ;AACX,YAAS,IAAI;AACb,YAAS;AAET,QAAK;;AAEP,SAAO;;;;;;;;;ACpBX,SAAgB,MAAM,IAA2B;AAC/C,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ocavue/utils",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"description": "A collection of utility functions for the browser and other environments",
|
|
6
6
|
"author": "ocavue <ocavue@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -55,16 +55,16 @@
|
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
|
-
"size-limit": [
|
|
59
|
-
{
|
|
60
|
-
"path": "dist/index.js"
|
|
61
|
-
}
|
|
62
|
-
],
|
|
63
58
|
"renovate": {
|
|
64
59
|
"extends": [
|
|
65
60
|
"github>ocavue/config-renovate"
|
|
66
61
|
]
|
|
67
62
|
},
|
|
63
|
+
"size-limit": [
|
|
64
|
+
{
|
|
65
|
+
"path": "dist/index.js"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
68
|
"scripts": {
|
|
69
69
|
"build": "tsdown",
|
|
70
70
|
"dev": "tsdown --watch",
|
package/src/default-map.test.ts
CHANGED
|
@@ -144,10 +144,13 @@ describe('DefaultMap', () => {
|
|
|
144
144
|
})
|
|
145
145
|
|
|
146
146
|
it('works with iteration methods', () => {
|
|
147
|
-
const map = new DefaultMap<string, number>(
|
|
148
|
-
|
|
149
|
-
[
|
|
150
|
-
|
|
147
|
+
const map = new DefaultMap<string, number>(
|
|
148
|
+
() => 0,
|
|
149
|
+
[
|
|
150
|
+
['a', 1],
|
|
151
|
+
['b', 2],
|
|
152
|
+
],
|
|
153
|
+
)
|
|
151
154
|
|
|
152
155
|
expect([...map.keys()]).toEqual(['a', 'b'])
|
|
153
156
|
expect([...map.values()]).toEqual([1, 2])
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { DefaultMap } from './default-map'
|
|
|
3
3
|
export * from './dom'
|
|
4
4
|
export { formatBytes } from './format-bytes'
|
|
5
5
|
export { getId } from './get-id'
|
|
6
|
+
export { isDeepEqual } from './is-deep-equal'
|
|
7
|
+
export { mapGroupBy } from './map-group-by'
|
|
8
|
+
export { objectGroupBy } from './object-group-by'
|
|
6
9
|
export { once } from './once'
|
|
7
10
|
export { sleep } from './sleep'
|
|
8
|
-
export { isDeepEqual } from './is-deep-equal'
|
|
@@ -59,8 +59,30 @@ describe('isDeepEqual', () => {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
it('handles nested arrays', () => {
|
|
62
|
-
expect(
|
|
63
|
-
|
|
62
|
+
expect(
|
|
63
|
+
isDeepEqual(
|
|
64
|
+
[
|
|
65
|
+
[1, 2],
|
|
66
|
+
[3, 4],
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
[1, 2],
|
|
70
|
+
[3, 4],
|
|
71
|
+
],
|
|
72
|
+
),
|
|
73
|
+
).toBe(true)
|
|
74
|
+
expect(
|
|
75
|
+
isDeepEqual(
|
|
76
|
+
[
|
|
77
|
+
[1, 2],
|
|
78
|
+
[3, 4],
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
[1, 2],
|
|
82
|
+
[3, 5],
|
|
83
|
+
],
|
|
84
|
+
),
|
|
85
|
+
).toBe(false)
|
|
64
86
|
})
|
|
65
87
|
})
|
|
66
88
|
|
|
@@ -105,9 +127,7 @@ describe('isDeepEqual', () => {
|
|
|
105
127
|
|
|
106
128
|
it('returns false for sets with different values', () => {
|
|
107
129
|
expect(isDeepEqual(new Set([1, 2, 3]), new Set([1, 2, 4]))).toBe(false)
|
|
108
|
-
expect(isDeepEqual(new Set(['a', 'b']), new Set(['a', 'c']))).toBe(
|
|
109
|
-
false,
|
|
110
|
-
)
|
|
130
|
+
expect(isDeepEqual(new Set(['a', 'b']), new Set(['a', 'c']))).toBe(false)
|
|
111
131
|
})
|
|
112
132
|
|
|
113
133
|
it('handles set order independence', () => {
|
|
@@ -145,44 +165,28 @@ describe('isDeepEqual', () => {
|
|
|
145
165
|
})
|
|
146
166
|
|
|
147
167
|
it('returns false for maps with different keys', () => {
|
|
148
|
-
expect(
|
|
149
|
-
isDeepEqual(new Map([['a', 1]]), new Map([['b', 1]])),
|
|
150
|
-
).toBe(false)
|
|
168
|
+
expect(isDeepEqual(new Map([['a', 1]]), new Map([['b', 1]]))).toBe(false)
|
|
151
169
|
})
|
|
152
170
|
|
|
153
171
|
it('returns false for maps with different values', () => {
|
|
154
|
-
expect(
|
|
155
|
-
isDeepEqual(new Map([['a', 1]]), new Map([['a', 2]])),
|
|
156
|
-
).toBe(false)
|
|
172
|
+
expect(isDeepEqual(new Map([['a', 1]]), new Map([['a', 2]]))).toBe(false)
|
|
157
173
|
})
|
|
158
174
|
|
|
159
175
|
it('handles nested values in maps', () => {
|
|
160
176
|
expect(
|
|
161
|
-
isDeepEqual(
|
|
162
|
-
new Map([['a', { b: 1 }]]),
|
|
163
|
-
new Map([['a', { b: 1 }]]),
|
|
164
|
-
),
|
|
177
|
+
isDeepEqual(new Map([['a', { b: 1 }]]), new Map([['a', { b: 1 }]])),
|
|
165
178
|
).toBe(true)
|
|
166
179
|
expect(
|
|
167
|
-
isDeepEqual(
|
|
168
|
-
new Map([['a', { b: 1 }]]),
|
|
169
|
-
new Map([['a', { b: 2 }]]),
|
|
170
|
-
),
|
|
180
|
+
isDeepEqual(new Map([['a', { b: 1 }]]), new Map([['a', { b: 2 }]])),
|
|
171
181
|
).toBe(false)
|
|
172
182
|
})
|
|
173
183
|
|
|
174
184
|
it('handles arrays as map values', () => {
|
|
175
185
|
expect(
|
|
176
|
-
isDeepEqual(
|
|
177
|
-
new Map([['a', [1, 2, 3]]]),
|
|
178
|
-
new Map([['a', [1, 2, 3]]]),
|
|
179
|
-
),
|
|
186
|
+
isDeepEqual(new Map([['a', [1, 2, 3]]]), new Map([['a', [1, 2, 3]]])),
|
|
180
187
|
).toBe(true)
|
|
181
188
|
expect(
|
|
182
|
-
isDeepEqual(
|
|
183
|
-
new Map([['a', [1, 2, 3]]]),
|
|
184
|
-
new Map([['a', [1, 2, 4]]]),
|
|
185
|
-
),
|
|
189
|
+
isDeepEqual(new Map([['a', [1, 2, 3]]]), new Map([['a', [1, 2, 4]]])),
|
|
186
190
|
).toBe(false)
|
|
187
191
|
})
|
|
188
192
|
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
mapGroupBy,
|
|
5
|
+
mapGroupByPolyfill,
|
|
6
|
+
mapGroupByNative,
|
|
7
|
+
} from './map-group-by'
|
|
8
|
+
|
|
9
|
+
const testCases = [
|
|
10
|
+
{ name: 'mapGroupBy', fn: mapGroupBy },
|
|
11
|
+
{ name: 'mapGroupByPolyfill', fn: mapGroupByPolyfill },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
if (!!Map.groupBy) {
|
|
15
|
+
testCases.push({ name: 'mapGroupByNative', fn: mapGroupByNative })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe.each(testCases)('$name', ({ fn }) => {
|
|
19
|
+
it('groups items by key', () => {
|
|
20
|
+
const items = [1, 2, 3, 4, 5, 6]
|
|
21
|
+
const result = fn(items, (item) => item % 2)
|
|
22
|
+
|
|
23
|
+
expect(result.get(0)).toEqual([2, 4, 6])
|
|
24
|
+
expect(result.get(1)).toEqual([1, 3, 5])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('works with string keys', () => {
|
|
28
|
+
const items = ['apple', 'banana', 'apricot', 'blueberry']
|
|
29
|
+
const result = fn(items, (item) => item[0])
|
|
30
|
+
|
|
31
|
+
expect(result.get('a')).toEqual(['apple', 'apricot'])
|
|
32
|
+
expect(result.get('b')).toEqual(['banana', 'blueberry'])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('works with object keys', () => {
|
|
36
|
+
const keyA = { type: 'A' }
|
|
37
|
+
const keyB = { type: 'B' }
|
|
38
|
+
const items = [1, 2, 3, 4]
|
|
39
|
+
const result = fn(items, (item) => (item % 2 === 0 ? keyA : keyB))
|
|
40
|
+
|
|
41
|
+
expect(result.get(keyA)).toEqual([2, 4])
|
|
42
|
+
expect(result.get(keyB)).toEqual([1, 3])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('passes index to key selector', () => {
|
|
46
|
+
const items = ['a', 'b', 'c', 'd']
|
|
47
|
+
const result = fn(items, (_, index) => Math.floor(index / 2))
|
|
48
|
+
|
|
49
|
+
expect(result.get(0)).toEqual(['a', 'b'])
|
|
50
|
+
expect(result.get(1)).toEqual(['c', 'd'])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('handles empty array', () => {
|
|
54
|
+
const result = fn([], (item) => item)
|
|
55
|
+
expect(result.size).toBe(0)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('handles single item', () => {
|
|
59
|
+
const result = fn([42], (item) => item)
|
|
60
|
+
expect(result.get(42)).toEqual([42])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('handles all items mapping to same key', () => {
|
|
64
|
+
const items = [1, 2, 3, 4]
|
|
65
|
+
const result = fn(items, () => 'same')
|
|
66
|
+
|
|
67
|
+
expect(result.size).toBe(1)
|
|
68
|
+
expect(result.get('same')).toEqual([1, 2, 3, 4])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('works with iterables other than arrays', () => {
|
|
72
|
+
const set = new Set([1, 2, 3, 4, 5])
|
|
73
|
+
const result = fn(set, (item) => item % 2)
|
|
74
|
+
|
|
75
|
+
expect(result.get(0)).toEqual([2, 4])
|
|
76
|
+
expect(result.get(1)).toEqual([1, 3, 5])
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
*/
|
|
4
|
+
export function mapGroupByPolyfill<K, T>(
|
|
5
|
+
items: Iterable<T>,
|
|
6
|
+
keySelector: (item: T, index: number) => K,
|
|
7
|
+
): Map<K, T[]> {
|
|
8
|
+
const map = new Map<K, T[]>()
|
|
9
|
+
let index = 0
|
|
10
|
+
for (const item of items) {
|
|
11
|
+
const key = keySelector(item, index)
|
|
12
|
+
const group = map.get(key)
|
|
13
|
+
if (group) {
|
|
14
|
+
group.push(item)
|
|
15
|
+
} else {
|
|
16
|
+
map.set(key, [item])
|
|
17
|
+
}
|
|
18
|
+
index++
|
|
19
|
+
}
|
|
20
|
+
return map
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function mapGroupByNative<K, T>(
|
|
27
|
+
items: Iterable<T>,
|
|
28
|
+
keySelector: (item: T, index: number) => K,
|
|
29
|
+
): Map<K, T[]> {
|
|
30
|
+
return Map.groupBy(items, keySelector)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A polyfill for the `Map.groupBy` static method.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export const mapGroupBy: <K, T>(
|
|
39
|
+
items: Iterable<T>,
|
|
40
|
+
keySelector: (item: T, index: number) => K,
|
|
41
|
+
) => Map<K, T[]> = !!Map.groupBy ? mapGroupByNative : mapGroupByPolyfill
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
objectGroupBy,
|
|
5
|
+
objectGroupByPolyfill,
|
|
6
|
+
objectGroupByNative,
|
|
7
|
+
} from './object-group-by'
|
|
8
|
+
|
|
9
|
+
const testCases = [
|
|
10
|
+
{ name: 'objectGroupBy', fn: objectGroupBy },
|
|
11
|
+
{ name: 'objectGroupByPolyfill', fn: objectGroupByPolyfill },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
if (!!Object.groupBy) {
|
|
15
|
+
testCases.push({ name: 'objectGroupByNative', fn: objectGroupByNative })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe.each(testCases)('$name', ({ fn }) => {
|
|
19
|
+
it('groups items by key', () => {
|
|
20
|
+
const items = [1, 2, 3, 4, 5, 6]
|
|
21
|
+
const result = fn(items, (item) => (item % 2 === 0 ? 'even' : 'odd'))
|
|
22
|
+
|
|
23
|
+
expect(result.even).toEqual([2, 4, 6])
|
|
24
|
+
expect(result.odd).toEqual([1, 3, 5])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('works with string keys', () => {
|
|
28
|
+
const items = ['apple', 'banana', 'apricot', 'blueberry']
|
|
29
|
+
const result = fn(items, (item) => item[0])
|
|
30
|
+
|
|
31
|
+
expect(result.a).toEqual(['apple', 'apricot'])
|
|
32
|
+
expect(result.b).toEqual(['banana', 'blueberry'])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('works with number keys', () => {
|
|
36
|
+
const items = ['a', 'b', 'c', 'd']
|
|
37
|
+
const result = fn(items, (_, index) => Math.floor(index / 2))
|
|
38
|
+
|
|
39
|
+
expect(result[0]).toEqual(['a', 'b'])
|
|
40
|
+
expect(result[1]).toEqual(['c', 'd'])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('works with symbol keys', () => {
|
|
44
|
+
const symA = Symbol('A')
|
|
45
|
+
const symB = Symbol('B')
|
|
46
|
+
const items = [1, 2, 3, 4]
|
|
47
|
+
const result = fn(items, (item) => (item % 2 === 0 ? symA : symB))
|
|
48
|
+
|
|
49
|
+
expect(result[symA]).toEqual([2, 4])
|
|
50
|
+
expect(result[symB]).toEqual([1, 3])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('passes index to key selector', () => {
|
|
54
|
+
const items = ['a', 'b', 'c', 'd']
|
|
55
|
+
const result = fn(items, (_, index) => (index < 2 ? 'first' : 'second'))
|
|
56
|
+
|
|
57
|
+
expect(result.first).toEqual(['a', 'b'])
|
|
58
|
+
expect(result.second).toEqual(['c', 'd'])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('handles empty array', () => {
|
|
62
|
+
const result = fn([], (item) => String(item))
|
|
63
|
+
expect(Object.keys(result)).toHaveLength(0)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('handles single item', () => {
|
|
67
|
+
const result = fn([42], (item) => String(item))
|
|
68
|
+
expect(result['42']).toEqual([42])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('handles all items mapping to same key', () => {
|
|
72
|
+
const items = [1, 2, 3, 4]
|
|
73
|
+
const result = fn(items, () => 'same')
|
|
74
|
+
|
|
75
|
+
expect(Object.keys(result)).toHaveLength(1)
|
|
76
|
+
expect(result.same).toEqual([1, 2, 3, 4])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('works with iterables other than arrays', () => {
|
|
80
|
+
const set = new Set([1, 2, 3, 4, 5])
|
|
81
|
+
const result = fn(set, (item) => (item % 2 === 0 ? 'even' : 'odd'))
|
|
82
|
+
|
|
83
|
+
expect(result.even).toEqual([2, 4])
|
|
84
|
+
expect(result.odd).toEqual([1, 3, 5])
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
*/
|
|
4
|
+
export function objectGroupByPolyfill<K extends PropertyKey, T>(
|
|
5
|
+
items: Iterable<T>,
|
|
6
|
+
keySelector: (item: T, index: number) => K,
|
|
7
|
+
): Partial<Record<K, T[]>> {
|
|
8
|
+
const result: Partial<Record<K, T[]>> = {}
|
|
9
|
+
let index = 0
|
|
10
|
+
for (const item of items) {
|
|
11
|
+
const key = keySelector(item, index)
|
|
12
|
+
const group = result[key]
|
|
13
|
+
if (group) {
|
|
14
|
+
group.push(item)
|
|
15
|
+
} else {
|
|
16
|
+
result[key] = [item]
|
|
17
|
+
}
|
|
18
|
+
index++
|
|
19
|
+
}
|
|
20
|
+
return result
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export function objectGroupByNative<K extends PropertyKey, T>(
|
|
27
|
+
items: Iterable<T>,
|
|
28
|
+
keySelector: (item: T, index: number) => K,
|
|
29
|
+
): Partial<Record<K, T[]>> {
|
|
30
|
+
return Object.groupBy(items, keySelector)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A polyfill for the `Object.groupBy` static method.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export const objectGroupBy: <K extends PropertyKey, T>(
|
|
39
|
+
items: Iterable<T>,
|
|
40
|
+
keySelector: (item: T, index: number) => K,
|
|
41
|
+
) => Partial<Record<K, T[]>> = !!Object.groupBy
|
|
42
|
+
? objectGroupByNative
|
|
43
|
+
: objectGroupByPolyfill
|