index.esm.js 15 KB

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