min-dash.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  3. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  4. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.MinDash = {}));
  5. })(this, (function (exports) { 'use strict';
  6. /**
  7. * Flatten array, one level deep.
  8. *
  9. * @template T
  10. *
  11. * @param {T[][] | T[] | null} [arr]
  12. *
  13. * @return {T[]}
  14. */
  15. function flatten(arr) {
  16. return Array.prototype.concat.apply([], arr);
  17. }
  18. const nativeToString = Object.prototype.toString;
  19. const nativeHasOwnProperty = Object.prototype.hasOwnProperty;
  20. function isUndefined(obj) {
  21. return obj === undefined;
  22. }
  23. function isDefined(obj) {
  24. return obj !== undefined;
  25. }
  26. function isNil(obj) {
  27. return obj == null;
  28. }
  29. function isArray(obj) {
  30. return nativeToString.call(obj) === '[object Array]';
  31. }
  32. function isObject(obj) {
  33. return nativeToString.call(obj) === '[object Object]';
  34. }
  35. function isNumber(obj) {
  36. return nativeToString.call(obj) === '[object Number]';
  37. }
  38. /**
  39. * @param {any} obj
  40. *
  41. * @return {boolean}
  42. */
  43. function isFunction(obj) {
  44. const tag = nativeToString.call(obj);
  45. return (
  46. tag === '[object Function]' ||
  47. tag === '[object AsyncFunction]' ||
  48. tag === '[object GeneratorFunction]' ||
  49. tag === '[object AsyncGeneratorFunction]' ||
  50. tag === '[object Proxy]'
  51. );
  52. }
  53. function isString(obj) {
  54. return nativeToString.call(obj) === '[object String]';
  55. }
  56. /**
  57. * Ensure collection is an array.
  58. *
  59. * @param {Object} obj
  60. */
  61. function ensureArray(obj) {
  62. if (isArray(obj)) {
  63. return;
  64. }
  65. throw new Error('must supply array');
  66. }
  67. /**
  68. * Return true, if target owns a property with the given key.
  69. *
  70. * @param {Object} target
  71. * @param {String} key
  72. *
  73. * @return {Boolean}
  74. */
  75. function has(target, key) {
  76. return nativeHasOwnProperty.call(target, key);
  77. }
  78. /**
  79. * @template T
  80. * @typedef { (
  81. * ((e: T) => boolean) |
  82. * ((e: T, idx: number) => boolean) |
  83. * ((e: T, key: string) => boolean) |
  84. * string |
  85. * number
  86. * ) } Matcher
  87. */
  88. /**
  89. * @template T
  90. * @template U
  91. *
  92. * @typedef { (
  93. * ((e: T) => U) | string | number
  94. * ) } Extractor
  95. */
  96. /**
  97. * @template T
  98. * @typedef { (val: T, key: any) => boolean } MatchFn
  99. */
  100. /**
  101. * @template T
  102. * @typedef { T[] } ArrayCollection
  103. */
  104. /**
  105. * @template T
  106. * @typedef { { [key: string]: T } } StringKeyValueCollection
  107. */
  108. /**
  109. * @template T
  110. * @typedef { { [key: number]: T } } NumberKeyValueCollection
  111. */
  112. /**
  113. * @template T
  114. * @typedef { StringKeyValueCollection<T> | NumberKeyValueCollection<T> } KeyValueCollection
  115. */
  116. /**
  117. * @template T
  118. * @typedef { KeyValueCollection<T> | ArrayCollection<T> } Collection
  119. */
  120. /**
  121. * Find element in collection.
  122. *
  123. * @template T
  124. * @param {Collection<T>} collection
  125. * @param {Matcher<T>} matcher
  126. *
  127. * @return {Object}
  128. */
  129. function find(collection, matcher) {
  130. const matchFn = toMatcher(matcher);
  131. let match;
  132. forEach(collection, function(val, key) {
  133. if (matchFn(val, key)) {
  134. match = val;
  135. return false;
  136. }
  137. });
  138. return match;
  139. }
  140. /**
  141. * Find element index in collection.
  142. *
  143. * @template T
  144. * @param {Collection<T>} collection
  145. * @param {Matcher<T>} matcher
  146. *
  147. * @return {number}
  148. */
  149. function findIndex(collection, matcher) {
  150. const matchFn = toMatcher(matcher);
  151. let idx = isArray(collection) ? -1 : undefined;
  152. forEach(collection, function(val, key) {
  153. if (matchFn(val, key)) {
  154. idx = key;
  155. return false;
  156. }
  157. });
  158. return idx;
  159. }
  160. /**
  161. * Filter elements in collection.
  162. *
  163. * @template T
  164. * @param {Collection<T>} collection
  165. * @param {Matcher<T>} matcher
  166. *
  167. * @return {T[]} result
  168. */
  169. function filter(collection, matcher) {
  170. const matchFn = toMatcher(matcher);
  171. let result = [];
  172. forEach(collection, function(val, key) {
  173. if (matchFn(val, key)) {
  174. result.push(val);
  175. }
  176. });
  177. return result;
  178. }
  179. /**
  180. * Iterate over collection; returning something
  181. * (non-undefined) will stop iteration.
  182. *
  183. * @template T
  184. * @param {Collection<T>} collection
  185. * @param { ((item: T, idx: number) => (boolean|void)) | ((item: T, key: string) => (boolean|void)) } iterator
  186. *
  187. * @return {T} return result that stopped the iteration
  188. */
  189. function forEach(collection, iterator) {
  190. let val,
  191. result;
  192. if (isUndefined(collection)) {
  193. return;
  194. }
  195. const convertKey = isArray(collection) ? toNum : identity;
  196. for (let key in collection) {
  197. if (has(collection, key)) {
  198. val = collection[key];
  199. result = iterator(val, convertKey(key));
  200. if (result === false) {
  201. return val;
  202. }
  203. }
  204. }
  205. }
  206. /**
  207. * Return collection without element.
  208. *
  209. * @template T
  210. * @param {ArrayCollection<T>} arr
  211. * @param {Matcher<T>} matcher
  212. *
  213. * @return {T[]}
  214. */
  215. function without(arr, matcher) {
  216. if (isUndefined(arr)) {
  217. return [];
  218. }
  219. ensureArray(arr);
  220. const matchFn = toMatcher(matcher);
  221. return arr.filter(function(el, idx) {
  222. return !matchFn(el, idx);
  223. });
  224. }
  225. /**
  226. * Reduce collection, returning a single result.
  227. *
  228. * @template T
  229. * @template V
  230. *
  231. * @param {Collection<T>} collection
  232. * @param {(result: V, entry: T, index: any) => V} iterator
  233. * @param {V} result
  234. *
  235. * @return {V} result returned from last iterator
  236. */
  237. function reduce(collection, iterator, result) {
  238. forEach(collection, function(value, idx) {
  239. result = iterator(result, value, idx);
  240. });
  241. return result;
  242. }
  243. /**
  244. * Return true if every element in the collection
  245. * matches the criteria.
  246. *
  247. * @param {Object|Array} collection
  248. * @param {Function} matcher
  249. *
  250. * @return {Boolean}
  251. */
  252. function every(collection, matcher) {
  253. return !!reduce(collection, function(matches, val, key) {
  254. return matches && matcher(val, key);
  255. }, true);
  256. }
  257. /**
  258. * Return true if some elements in the collection
  259. * match the criteria.
  260. *
  261. * @param {Object|Array} collection
  262. * @param {Function} matcher
  263. *
  264. * @return {Boolean}
  265. */
  266. function some(collection, matcher) {
  267. return !!find(collection, matcher);
  268. }
  269. /**
  270. * Transform a collection into another collection
  271. * by piping each member through the given fn.
  272. *
  273. * @param {Object|Array} collection
  274. * @param {Function} fn
  275. *
  276. * @return {Array} transformed collection
  277. */
  278. function map(collection, fn) {
  279. let result = [];
  280. forEach(collection, function(val, key) {
  281. result.push(fn(val, key));
  282. });
  283. return result;
  284. }
  285. /**
  286. * Get the collections keys.
  287. *
  288. * @param {Object|Array} collection
  289. *
  290. * @return {Array}
  291. */
  292. function keys(collection) {
  293. return collection && Object.keys(collection) || [];
  294. }
  295. /**
  296. * Shorthand for `keys(o).length`.
  297. *
  298. * @param {Object|Array} collection
  299. *
  300. * @return {Number}
  301. */
  302. function size(collection) {
  303. return keys(collection).length;
  304. }
  305. /**
  306. * Get the values in the collection.
  307. *
  308. * @param {Object|Array} collection
  309. *
  310. * @return {Array}
  311. */
  312. function values(collection) {
  313. return map(collection, (val) => val);
  314. }
  315. /**
  316. * Group collection members by attribute.
  317. *
  318. * @param {Object|Array} collection
  319. * @param {Extractor} extractor
  320. *
  321. * @return {Object} map with { attrValue => [ a, b, c ] }
  322. */
  323. function groupBy(collection, extractor, grouped = {}) {
  324. extractor = toExtractor(extractor);
  325. forEach(collection, function(val) {
  326. let discriminator = extractor(val) || '_';
  327. let group = grouped[discriminator];
  328. if (!group) {
  329. group = grouped[discriminator] = [];
  330. }
  331. group.push(val);
  332. });
  333. return grouped;
  334. }
  335. function uniqueBy(extractor, ...collections) {
  336. extractor = toExtractor(extractor);
  337. let grouped = {};
  338. forEach(collections, (c) => groupBy(c, extractor, grouped));
  339. let result = map(grouped, function(val, key) {
  340. return val[0];
  341. });
  342. return result;
  343. }
  344. const unionBy = uniqueBy;
  345. /**
  346. * Sort collection by criteria.
  347. *
  348. * @template T
  349. *
  350. * @param {Collection<T>} collection
  351. * @param {Extractor<T, number | string>} extractor
  352. *
  353. * @return {Array}
  354. */
  355. function sortBy(collection, extractor) {
  356. extractor = toExtractor(extractor);
  357. let sorted = [];
  358. forEach(collection, function(value, key) {
  359. let disc = extractor(value, key);
  360. let entry = {
  361. d: disc,
  362. v: value
  363. };
  364. for (var idx = 0; idx < sorted.length; idx++) {
  365. let { d } = sorted[idx];
  366. if (disc < d) {
  367. sorted.splice(idx, 0, entry);
  368. return;
  369. }
  370. }
  371. // not inserted, append (!)
  372. sorted.push(entry);
  373. });
  374. return map(sorted, (e) => e.v);
  375. }
  376. /**
  377. * Create an object pattern matcher.
  378. *
  379. * @example
  380. *
  381. * ```javascript
  382. * const matcher = matchPattern({ id: 1 });
  383. *
  384. * let element = find(elements, matcher);
  385. * ```
  386. *
  387. * @template T
  388. *
  389. * @param {T} pattern
  390. *
  391. * @return { (el: any) => boolean } matcherFn
  392. */
  393. function matchPattern(pattern) {
  394. return function(el) {
  395. return every(pattern, function(val, key) {
  396. return el[key] === val;
  397. });
  398. };
  399. }
  400. /**
  401. * @param {string | ((e: any) => any) } extractor
  402. *
  403. * @return { (e: any) => any }
  404. */
  405. function toExtractor(extractor) {
  406. /**
  407. * @satisfies { (e: any) => any }
  408. */
  409. return isFunction(extractor) ? extractor : (e) => {
  410. // @ts-ignore: just works
  411. return e[extractor];
  412. };
  413. }
  414. /**
  415. * @template T
  416. * @param {Matcher<T>} matcher
  417. *
  418. * @return {MatchFn<T>}
  419. */
  420. function toMatcher(matcher) {
  421. return isFunction(matcher) ? matcher : (e) => {
  422. return e === matcher;
  423. };
  424. }
  425. function identity(arg) {
  426. return arg;
  427. }
  428. function toNum(arg) {
  429. return Number(arg);
  430. }
  431. /* global setTimeout clearTimeout */
  432. /**
  433. * @typedef { {
  434. * (...args: any[]): any;
  435. * flush: () => void;
  436. * cancel: () => void;
  437. * } } DebouncedFunction
  438. */
  439. /**
  440. * Debounce fn, calling it only once if the given time
  441. * elapsed between calls.
  442. *
  443. * Lodash-style the function exposes methods to `#clear`
  444. * and `#flush` to control internal behavior.
  445. *
  446. * @param {Function} fn
  447. * @param {Number} timeout
  448. *
  449. * @return {DebouncedFunction} debounced function
  450. */
  451. function debounce(fn, timeout) {
  452. let timer;
  453. let lastArgs;
  454. let lastThis;
  455. let lastNow;
  456. function fire(force) {
  457. let now = Date.now();
  458. let scheduledDiff = force ? 0 : (lastNow + timeout) - now;
  459. if (scheduledDiff > 0) {
  460. return schedule(scheduledDiff);
  461. }
  462. fn.apply(lastThis, lastArgs);
  463. clear();
  464. }
  465. function schedule(timeout) {
  466. timer = setTimeout(fire, timeout);
  467. }
  468. function clear() {
  469. if (timer) {
  470. clearTimeout(timer);
  471. }
  472. timer = lastNow = lastArgs = lastThis = undefined;
  473. }
  474. function flush() {
  475. if (timer) {
  476. fire(true);
  477. }
  478. clear();
  479. }
  480. /**
  481. * @type { DebouncedFunction }
  482. */
  483. function callback(...args) {
  484. lastNow = Date.now();
  485. lastArgs = args;
  486. lastThis = this;
  487. // ensure an execution is scheduled
  488. if (!timer) {
  489. schedule(timeout);
  490. }
  491. }
  492. callback.flush = flush;
  493. callback.cancel = clear;
  494. return callback;
  495. }
  496. /**
  497. * Throttle fn, calling at most once
  498. * in the given interval.
  499. *
  500. * @param {Function} fn
  501. * @param {Number} interval
  502. *
  503. * @return {Function} throttled function
  504. */
  505. function throttle(fn, interval) {
  506. let throttling = false;
  507. return function(...args) {
  508. if (throttling) {
  509. return;
  510. }
  511. fn(...args);
  512. throttling = true;
  513. setTimeout(() => {
  514. throttling = false;
  515. }, interval);
  516. };
  517. }
  518. /**
  519. * Bind function against target <this>.
  520. *
  521. * @param {Function} fn
  522. * @param {Object} target
  523. *
  524. * @return {Function} bound function
  525. */
  526. function bind(fn, target) {
  527. return fn.bind(target);
  528. }
  529. /**
  530. * Convenience wrapper for `Object.assign`.
  531. *
  532. * @param {Object} target
  533. * @param {...Object} others
  534. *
  535. * @return {Object} the target
  536. */
  537. function assign(target, ...others) {
  538. return Object.assign(target, ...others);
  539. }
  540. /**
  541. * Sets a nested property of a given object to the specified value.
  542. *
  543. * This mutates the object and returns it.
  544. *
  545. * @template T
  546. *
  547. * @param {T} target The target of the set operation.
  548. * @param {(string|number)[]} path The path to the nested value.
  549. * @param {any} value The value to set.
  550. *
  551. * @return {T}
  552. */
  553. function set(target, path, value) {
  554. let currentTarget = target;
  555. forEach(path, function(key, idx) {
  556. if (typeof key !== 'number' && typeof key !== 'string') {
  557. throw new Error('illegal key type: ' + typeof key + '. Key should be of type number or string.');
  558. }
  559. if (key === 'constructor') {
  560. throw new Error('illegal key: constructor');
  561. }
  562. if (key === '__proto__') {
  563. throw new Error('illegal key: __proto__');
  564. }
  565. let nextKey = path[idx + 1];
  566. let nextTarget = currentTarget[key];
  567. if (isDefined(nextKey) && isNil(nextTarget)) {
  568. nextTarget = currentTarget[key] = isNaN(+nextKey) ? {} : [];
  569. }
  570. if (isUndefined(nextKey)) {
  571. if (isUndefined(value)) {
  572. delete currentTarget[key];
  573. } else {
  574. currentTarget[key] = value;
  575. }
  576. } else {
  577. currentTarget = nextTarget;
  578. }
  579. });
  580. return target;
  581. }
  582. /**
  583. * Gets a nested property of a given object.
  584. *
  585. * @param {Object} target The target of the get operation.
  586. * @param {(string|number)[]} path The path to the nested value.
  587. * @param {any} [defaultValue] The value to return if no value exists.
  588. *
  589. * @return {any}
  590. */
  591. function get(target, path, defaultValue) {
  592. let currentTarget = target;
  593. forEach(path, function(key) {
  594. // accessing nil property yields <undefined>
  595. if (isNil(currentTarget)) {
  596. currentTarget = undefined;
  597. return false;
  598. }
  599. currentTarget = currentTarget[key];
  600. });
  601. return isUndefined(currentTarget) ? defaultValue : currentTarget;
  602. }
  603. /**
  604. * Pick properties from the given target.
  605. *
  606. * @template T
  607. * @template {any[]} V
  608. *
  609. * @param {T} target
  610. * @param {V} properties
  611. *
  612. * @return Pick<T, V>
  613. */
  614. function pick(target, properties) {
  615. let result = {};
  616. let obj = Object(target);
  617. forEach(properties, function(prop) {
  618. if (prop in obj) {
  619. result[prop] = target[prop];
  620. }
  621. });
  622. return result;
  623. }
  624. /**
  625. * Pick all target properties, excluding the given ones.
  626. *
  627. * @template T
  628. * @template {any[]} V
  629. *
  630. * @param {T} target
  631. * @param {V} properties
  632. *
  633. * @return {Omit<T, V>} target
  634. */
  635. function omit(target, properties) {
  636. let result = {};
  637. let obj = Object(target);
  638. forEach(obj, function(prop, key) {
  639. if (properties.indexOf(key) === -1) {
  640. result[key] = prop;
  641. }
  642. });
  643. return result;
  644. }
  645. /**
  646. * Recursively merge `...sources` into given target.
  647. *
  648. * Does support merging objects; does not support merging arrays.
  649. *
  650. * @param {Object} target
  651. * @param {...Object} sources
  652. *
  653. * @return {Object} the target
  654. */
  655. function merge(target, ...sources) {
  656. if (!sources.length) {
  657. return target;
  658. }
  659. forEach(sources, function(source) {
  660. // skip non-obj sources, i.e. null
  661. if (!source || !isObject(source)) {
  662. return;
  663. }
  664. forEach(source, function(sourceVal, key) {
  665. if (key === '__proto__') {
  666. return;
  667. }
  668. let targetVal = target[key];
  669. if (isObject(sourceVal)) {
  670. if (!isObject(targetVal)) {
  671. // override target[key] with object
  672. targetVal = {};
  673. }
  674. target[key] = merge(targetVal, sourceVal);
  675. } else {
  676. target[key] = sourceVal;
  677. }
  678. });
  679. });
  680. return target;
  681. }
  682. exports.assign = assign;
  683. exports.bind = bind;
  684. exports.debounce = debounce;
  685. exports.ensureArray = ensureArray;
  686. exports.every = every;
  687. exports.filter = filter;
  688. exports.find = find;
  689. exports.findIndex = findIndex;
  690. exports.flatten = flatten;
  691. exports.forEach = forEach;
  692. exports.get = get;
  693. exports.groupBy = groupBy;
  694. exports.has = has;
  695. exports.isArray = isArray;
  696. exports.isDefined = isDefined;
  697. exports.isFunction = isFunction;
  698. exports.isNil = isNil;
  699. exports.isNumber = isNumber;
  700. exports.isObject = isObject;
  701. exports.isString = isString;
  702. exports.isUndefined = isUndefined;
  703. exports.keys = keys;
  704. exports.map = map;
  705. exports.matchPattern = matchPattern;
  706. exports.merge = merge;
  707. exports.omit = omit;
  708. exports.pick = pick;
  709. exports.reduce = reduce;
  710. exports.set = set;
  711. exports.size = size;
  712. exports.some = some;
  713. exports.sortBy = sortBy;
  714. exports.throttle = throttle;
  715. exports.unionBy = unionBy;
  716. exports.uniqueBy = uniqueBy;
  717. exports.values = values;
  718. exports.without = without;
  719. }));