vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1613 lines
42 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
9 years ago
11 years ago
11 years ago
11 years ago
10 years ago
10 years ago
11 years ago
11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
9 years ago
  1. // utility functions
  2. // first check if moment.js is already loaded in the browser window, if so,
  3. // use this instance. Else, load via commonjs.
  4. var moment = require('./module/moment');
  5. var uuid = require('./module/uuid');
  6. /**
  7. * Test whether given object is a number
  8. * @param {*} object
  9. * @return {Boolean} isNumber
  10. */
  11. exports.isNumber = function (object) {
  12. return (object instanceof Number || typeof object == 'number');
  13. };
  14. /**
  15. * Remove everything in the DOM object
  16. * @param {Element} DOMobject
  17. */
  18. exports.recursiveDOMDelete = function (DOMobject) {
  19. if (DOMobject) {
  20. while (DOMobject.hasChildNodes() === true) {
  21. exports.recursiveDOMDelete(DOMobject.firstChild);
  22. DOMobject.removeChild(DOMobject.firstChild);
  23. }
  24. }
  25. };
  26. /**
  27. * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value.
  28. *
  29. * @param {number} min
  30. * @param {number} max
  31. * @param {number} total
  32. * @param {number} value
  33. * @returns {number}
  34. */
  35. exports.giveRange = function (min, max, total, value) {
  36. if (max == min) {
  37. return 0.5;
  38. }
  39. else {
  40. var scale = 1 / (max - min);
  41. return Math.max(0, (value - min) * scale);
  42. }
  43. };
  44. /**
  45. * Test whether given object is a string
  46. * @param {*} object
  47. * @return {Boolean} isString
  48. */
  49. exports.isString = function (object) {
  50. return (object instanceof String || typeof object == 'string');
  51. };
  52. /**
  53. * Test whether given object is a Date, or a String containing a Date
  54. * @param {Date | String} object
  55. * @return {Boolean} isDate
  56. */
  57. exports.isDate = function (object) {
  58. if (object instanceof Date) {
  59. return true;
  60. }
  61. else if (exports.isString(object)) {
  62. // test whether this string contains a date
  63. var match = ASPDateRegex.exec(object);
  64. if (match) {
  65. return true;
  66. }
  67. else if (!isNaN(Date.parse(object))) {
  68. return true;
  69. }
  70. }
  71. return false;
  72. };
  73. /**
  74. * Create a semi UUID
  75. * source: http://stackoverflow.com/a/105074/1262753
  76. * @return {String} uuid
  77. */
  78. exports.randomUUID = function () {
  79. return uuid.v4();
  80. };
  81. /**
  82. * assign all keys of an object that are not nested objects to a certain value (used for color objects).
  83. * @param {object} obj
  84. * @param {number} value
  85. */
  86. exports.assignAllKeys = function (obj, value) {
  87. for (var prop in obj) {
  88. if (obj.hasOwnProperty(prop)) {
  89. if (typeof obj[prop] !== 'object') {
  90. obj[prop] = value;
  91. }
  92. }
  93. }
  94. };
  95. /**
  96. * Fill an object with a possibly partially defined other object. Only copies values if the a object has an object requiring values.
  97. * That means an object is not created on a property if only the b object has it.
  98. * @param {object} a
  99. * @param {object} b
  100. * @param {boolean} [allowDeletion=false]
  101. */
  102. exports.fillIfDefined = function (a, b, allowDeletion = false) {
  103. for (var prop in a) {
  104. if (b[prop] !== undefined) {
  105. if (typeof b[prop] !== 'object') {
  106. if ((b[prop] === undefined || b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  107. delete a[prop];
  108. }
  109. else {
  110. a[prop] = b[prop];
  111. }
  112. }
  113. else {
  114. if (typeof a[prop] === 'object') {
  115. exports.fillIfDefined(a[prop], b[prop], allowDeletion);
  116. }
  117. }
  118. }
  119. }
  120. };
  121. /**
  122. * Extend object a with the properties of object b or a series of objects
  123. * Only properties with defined values are copied
  124. * @param {Object} a
  125. * @param {... Object} b
  126. * @return {Object} a
  127. */
  128. exports.protoExtend = function (a, b) { // eslint-disable-line no-unused-vars
  129. for (var i = 1; i < arguments.length; i++) {
  130. var other = arguments[i];
  131. for (var prop in other) {
  132. a[prop] = other[prop];
  133. }
  134. }
  135. return a;
  136. };
  137. /**
  138. * Extend object a with the properties of object b or a series of objects
  139. * Only properties with defined values are copied
  140. * @param {Object} a
  141. * @param {... Object} b
  142. * @return {Object} a
  143. */
  144. exports.extend = function (a, b) { // eslint-disable-line no-unused-vars
  145. for (var i = 1; i < arguments.length; i++) {
  146. var other = arguments[i];
  147. for (var prop in other) {
  148. if (other.hasOwnProperty(prop)) {
  149. a[prop] = other[prop];
  150. }
  151. }
  152. }
  153. return a;
  154. };
  155. /**
  156. * Extend object a with selected properties of object b or a series of objects
  157. * Only properties with defined values are copied
  158. * @param {Array.<String>} props
  159. * @param {Object} a
  160. * @param {Object} b
  161. * @return {Object} a
  162. */
  163. exports.selectiveExtend = function (props, a, b) { // eslint-disable-line no-unused-vars
  164. if (!Array.isArray(props)) {
  165. throw new Error('Array with property names expected as first argument');
  166. }
  167. for (var i = 2; i < arguments.length; i++) {
  168. var other = arguments[i];
  169. for (var p = 0; p < props.length; p++) {
  170. var prop = props[p];
  171. if (other && other.hasOwnProperty(prop)) {
  172. a[prop] = other[prop];
  173. }
  174. }
  175. }
  176. return a;
  177. };
  178. /**
  179. * Extend object a with selected properties of object b or a series of objects
  180. * Only properties with defined values are copied
  181. * @param {Array.<String>} props
  182. * @param {Object} a
  183. * @param {Object} b
  184. * @param {Boolean} [allowDeletion=false]
  185. * @return {Object} a
  186. */
  187. exports.selectiveDeepExtend = function (props, a, b, allowDeletion = false) {
  188. // TODO: add support for Arrays to deepExtend
  189. if (Array.isArray(b)) {
  190. throw new TypeError('Arrays are not supported by deepExtend');
  191. }
  192. for (var i = 2; i < arguments.length; i++) {
  193. var other = arguments[i];
  194. for (var p = 0; p < props.length; p++) {
  195. var prop = props[p];
  196. if (other.hasOwnProperty(prop)) {
  197. if (b[prop] && b[prop].constructor === Object) {
  198. if (a[prop] === undefined) {
  199. a[prop] = {};
  200. }
  201. if (a[prop].constructor === Object) {
  202. exports.deepExtend(a[prop], b[prop], false, allowDeletion);
  203. }
  204. else {
  205. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  206. delete a[prop];
  207. }
  208. else {
  209. a[prop] = b[prop];
  210. }
  211. }
  212. } else if (Array.isArray(b[prop])) {
  213. throw new TypeError('Arrays are not supported by deepExtend');
  214. } else {
  215. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  216. delete a[prop];
  217. }
  218. else {
  219. a[prop] = b[prop];
  220. }
  221. }
  222. }
  223. }
  224. }
  225. return a;
  226. };
  227. /**
  228. * Extend object a with selected properties of object b or a series of objects
  229. * Only properties with defined values are copied
  230. * @param {Array.<String>} props
  231. * @param {Object} a
  232. * @param {Object} b
  233. * @param {Boolean} [allowDeletion=false]
  234. * @return {Object} a
  235. */
  236. exports.selectiveNotDeepExtend = function (props, a, b, allowDeletion = false) {
  237. // TODO: add support for Arrays to deepExtend
  238. if (Array.isArray(b)) {
  239. throw new TypeError('Arrays are not supported by deepExtend');
  240. }
  241. for (var prop in b) {
  242. if (b.hasOwnProperty(prop)) {
  243. if (props.indexOf(prop) == -1) {
  244. if (b[prop] && b[prop].constructor === Object) {
  245. if (a[prop] === undefined) {
  246. a[prop] = {};
  247. }
  248. if (a[prop].constructor === Object) {
  249. exports.deepExtend(a[prop], b[prop]);
  250. }
  251. else {
  252. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  253. delete a[prop];
  254. }
  255. else {
  256. a[prop] = b[prop];
  257. }
  258. }
  259. } else if (Array.isArray(b[prop])) {
  260. a[prop] = [];
  261. for (let i = 0; i < b[prop].length; i++) {
  262. a[prop].push(b[prop][i]);
  263. }
  264. } else {
  265. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  266. delete a[prop];
  267. }
  268. else {
  269. a[prop] = b[prop];
  270. }
  271. }
  272. }
  273. }
  274. }
  275. return a;
  276. };
  277. /**
  278. * Deep extend an object a with the properties of object b
  279. * @param {Object} a
  280. * @param {Object} b
  281. * @param {Boolean} [protoExtend] --> optional parameter. If true, the prototype values will also be extended.
  282. * (ie. the options objects that inherit from others will also get the inherited options)
  283. * @param {Boolean} [allowDeletion] --> optional parameter. If true, the values of fields that are null will not deleted
  284. * @returns {Object}
  285. */
  286. exports.deepExtend = function (a, b, protoExtend, allowDeletion) {
  287. for (var prop in b) {
  288. if (b.hasOwnProperty(prop) || protoExtend === true) {
  289. if (b[prop] && b[prop].constructor === Object) {
  290. if (a[prop] === undefined) {
  291. a[prop] = {};
  292. }
  293. if (a[prop].constructor === Object) {
  294. exports.deepExtend(a[prop], b[prop], protoExtend);
  295. }
  296. else {
  297. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  298. delete a[prop];
  299. }
  300. else {
  301. a[prop] = b[prop];
  302. }
  303. }
  304. } else if (Array.isArray(b[prop])) {
  305. a[prop] = [];
  306. for (let i = 0; i < b[prop].length; i++) {
  307. a[prop].push(b[prop][i]);
  308. }
  309. } else {
  310. if ((b[prop] === null) && a[prop] !== undefined && allowDeletion === true) {
  311. delete a[prop];
  312. }
  313. else {
  314. a[prop] = b[prop];
  315. }
  316. }
  317. }
  318. }
  319. return a;
  320. };
  321. /**
  322. * Test whether all elements in two arrays are equal.
  323. * @param {Array} a
  324. * @param {Array} b
  325. * @return {boolean} Returns true if both arrays have the same length and same
  326. * elements.
  327. */
  328. exports.equalArray = function (a, b) {
  329. if (a.length != b.length) return false;
  330. for (var i = 0, len = a.length; i < len; i++) {
  331. if (a[i] != b[i]) return false;
  332. }
  333. return true;
  334. };
  335. /**
  336. * Convert an object to another type
  337. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  338. * @param {String | undefined} type Name of the type. Available types:
  339. * 'Boolean', 'Number', 'String',
  340. * 'Date', 'Moment', ISODate', 'ASPDate'.
  341. * @return {*} object
  342. * @throws Error
  343. */
  344. exports.convert = function (object, type) {
  345. var match;
  346. if (object === undefined) {
  347. return undefined;
  348. }
  349. if (object === null) {
  350. return null;
  351. }
  352. if (!type) {
  353. return object;
  354. }
  355. if (!(typeof type === 'string') && !(type instanceof String)) {
  356. throw new Error('Type must be a string');
  357. }
  358. //noinspection FallthroughInSwitchStatementJS
  359. switch (type) {
  360. case 'boolean':
  361. case 'Boolean':
  362. return Boolean(object);
  363. case 'number':
  364. case 'Number':
  365. if (exports.isString(object) && !isNaN(Date.parse(object))) {
  366. return moment(object).valueOf();
  367. } else {
  368. return Number(object.valueOf());
  369. }
  370. case 'string':
  371. case 'String':
  372. return String(object);
  373. case 'Date':
  374. if (exports.isNumber(object)) {
  375. return new Date(object);
  376. }
  377. if (object instanceof Date) {
  378. return new Date(object.valueOf());
  379. }
  380. else if (moment.isMoment(object)) {
  381. return new Date(object.valueOf());
  382. }
  383. if (exports.isString(object)) {
  384. match = ASPDateRegex.exec(object);
  385. if (match) {
  386. // object is an ASP date
  387. return new Date(Number(match[1])); // parse number
  388. }
  389. else {
  390. return moment(new Date(object)).toDate(); // parse string
  391. }
  392. }
  393. else {
  394. throw new Error(
  395. 'Cannot convert object of type ' + exports.getType(object) +
  396. ' to type Date');
  397. }
  398. case 'Moment':
  399. if (exports.isNumber(object)) {
  400. return moment(object);
  401. }
  402. if (object instanceof Date) {
  403. return moment(object.valueOf());
  404. }
  405. else if (moment.isMoment(object)) {
  406. return moment(object);
  407. }
  408. if (exports.isString(object)) {
  409. match = ASPDateRegex.exec(object);
  410. if (match) {
  411. // object is an ASP date
  412. return moment(Number(match[1])); // parse number
  413. }
  414. else {
  415. return moment(object); // parse string
  416. }
  417. }
  418. else {
  419. throw new Error(
  420. 'Cannot convert object of type ' + exports.getType(object) +
  421. ' to type Date');
  422. }
  423. case 'ISODate':
  424. if (exports.isNumber(object)) {
  425. return new Date(object);
  426. }
  427. else if (object instanceof Date) {
  428. return object.toISOString();
  429. }
  430. else if (moment.isMoment(object)) {
  431. return object.toDate().toISOString();
  432. }
  433. else if (exports.isString(object)) {
  434. match = ASPDateRegex.exec(object);
  435. if (match) {
  436. // object is an ASP date
  437. return new Date(Number(match[1])).toISOString(); // parse number
  438. }
  439. else {
  440. return moment(object).format(); // ISO 8601
  441. }
  442. }
  443. else {
  444. throw new Error(
  445. 'Cannot convert object of type ' + exports.getType(object) +
  446. ' to type ISODate');
  447. }
  448. case 'ASPDate':
  449. if (exports.isNumber(object)) {
  450. return '/Date(' + object + ')/';
  451. }
  452. else if (object instanceof Date) {
  453. return '/Date(' + object.valueOf() + ')/';
  454. }
  455. else if (exports.isString(object)) {
  456. match = ASPDateRegex.exec(object);
  457. var value;
  458. if (match) {
  459. // object is an ASP date
  460. value = new Date(Number(match[1])).valueOf(); // parse number
  461. }
  462. else {
  463. value = new Date(object).valueOf(); // parse string
  464. }
  465. return '/Date(' + value + ')/';
  466. }
  467. else {
  468. throw new Error(
  469. 'Cannot convert object of type ' + exports.getType(object) +
  470. ' to type ASPDate');
  471. }
  472. default:
  473. throw new Error('Unknown type "' + type + '"');
  474. }
  475. };
  476. // parse ASP.Net Date pattern,
  477. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  478. // code from http://momentjs.com/
  479. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  480. /**
  481. * Get the type of an object, for example exports.getType([]) returns 'Array'
  482. * @param {*} object
  483. * @return {String} type
  484. */
  485. exports.getType = function (object) {
  486. var type = typeof object;
  487. if (type == 'object') {
  488. if (object === null) {
  489. return 'null';
  490. }
  491. if (object instanceof Boolean) {
  492. return 'Boolean';
  493. }
  494. if (object instanceof Number) {
  495. return 'Number';
  496. }
  497. if (object instanceof String) {
  498. return 'String';
  499. }
  500. if (Array.isArray(object)) {
  501. return 'Array';
  502. }
  503. if (object instanceof Date) {
  504. return 'Date';
  505. }
  506. return 'Object';
  507. }
  508. else if (type == 'number') {
  509. return 'Number';
  510. }
  511. else if (type == 'boolean') {
  512. return 'Boolean';
  513. }
  514. else if (type == 'string') {
  515. return 'String';
  516. }
  517. else if (type === undefined) {
  518. return 'undefined';
  519. }
  520. return type;
  521. };
  522. /**
  523. * Used to extend an array and copy it. This is used to propagate paths recursively.
  524. *
  525. * @param {Array} arr
  526. * @param {*} newValue
  527. * @returns {Array}
  528. */
  529. exports.copyAndExtendArray = function (arr, newValue) {
  530. let newArr = [];
  531. for (let i = 0; i < arr.length; i++) {
  532. newArr.push(arr[i]);
  533. }
  534. newArr.push(newValue);
  535. return newArr;
  536. };
  537. /**
  538. * Used to extend an array and copy it. This is used to propagate paths recursively.
  539. *
  540. * @param {Array} arr
  541. * @returns {Array}
  542. */
  543. exports.copyArray = function (arr) {
  544. let newArr = [];
  545. for (let i = 0; i < arr.length; i++) {
  546. newArr.push(arr[i]);
  547. }
  548. return newArr;
  549. };
  550. /**
  551. * Retrieve the absolute left value of a DOM element
  552. * @param {Element} elem A dom element, for example a div
  553. * @return {number} left The absolute left position of this element
  554. * in the browser page.
  555. */
  556. exports.getAbsoluteLeft = function (elem) {
  557. return elem.getBoundingClientRect().left;
  558. };
  559. exports.getAbsoluteRight = function (elem) {
  560. return elem.getBoundingClientRect().right;
  561. };
  562. /**
  563. * Retrieve the absolute top value of a DOM element
  564. * @param {Element} elem A dom element, for example a div
  565. * @return {number} top The absolute top position of this element
  566. * in the browser page.
  567. */
  568. exports.getAbsoluteTop = function (elem) {
  569. return elem.getBoundingClientRect().top;
  570. };
  571. /**
  572. * add a className to the given elements style
  573. * @param {Element} elem
  574. * @param {String} classNames
  575. */
  576. exports.addClassName = function (elem, classNames) {
  577. var classes = elem.className.split(' ');
  578. var newClasses = classNames.split(' ');
  579. classes = classes.concat(newClasses.filter(function(className) {
  580. return classes.indexOf(className) < 0;
  581. }));
  582. elem.className = classes.join(' ');
  583. };
  584. /**
  585. * add a className to the given elements style
  586. * @param {Element} elem
  587. * @param {String} classNames
  588. */
  589. exports.removeClassName = function (elem, classNames) {
  590. var classes = elem.className.split(' ');
  591. var oldClasses = classNames.split(' ');
  592. classes = classes.filter(function(className) {
  593. return oldClasses.indexOf(className) < 0;
  594. });
  595. elem.className = classes.join(' ');
  596. };
  597. /**
  598. * For each method for both arrays and objects.
  599. * In case of an array, the built-in Array.forEach() is applied.
  600. * In case of an Object, the method loops over all properties of the object.
  601. * @param {Object | Array} object An Object or Array
  602. * @param {function} callback Callback method, called for each item in
  603. * the object or array with three parameters:
  604. * callback(value, index, object)
  605. */
  606. exports.forEach = function (object, callback) {
  607. var i,
  608. len;
  609. if (Array.isArray(object)) {
  610. // array
  611. for (i = 0, len = object.length; i < len; i++) {
  612. callback(object[i], i, object);
  613. }
  614. }
  615. else {
  616. // object
  617. for (i in object) {
  618. if (object.hasOwnProperty(i)) {
  619. callback(object[i], i, object);
  620. }
  621. }
  622. }
  623. };
  624. /**
  625. * Convert an object into an array: all objects properties are put into the
  626. * array. The resulting array is unordered.
  627. * @param {Object} object
  628. * @returns {Array} array
  629. */
  630. exports.toArray = function (object) {
  631. var array = [];
  632. for (var prop in object) {
  633. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  634. }
  635. return array;
  636. };
  637. /**
  638. * Update a property in an object
  639. * @param {Object} object
  640. * @param {String} key
  641. * @param {*} value
  642. * @return {Boolean} changed
  643. */
  644. exports.updateProperty = function (object, key, value) {
  645. if (object[key] !== value) {
  646. object[key] = value;
  647. return true;
  648. }
  649. else {
  650. return false;
  651. }
  652. };
  653. /**
  654. * Throttle the given function to be only executed once per animation frame
  655. * @param {function} fn
  656. * @returns {function} Returns the throttled function
  657. */
  658. exports.throttle = function (fn) {
  659. var scheduled = false;
  660. return function throttled () {
  661. if (!scheduled) {
  662. scheduled = true;
  663. requestAnimationFrame(function () {
  664. scheduled = false;
  665. fn();
  666. });
  667. }
  668. }
  669. };
  670. /**
  671. * Add and event listener. Works for all browsers
  672. * @param {Element} element An html element
  673. * @param {string} action The action, for example "click",
  674. * without the prefix "on"
  675. * @param {function} listener The callback function to be executed
  676. * @param {boolean} [useCapture]
  677. */
  678. exports.addEventListener = function (element, action, listener, useCapture) {
  679. if (element.addEventListener) {
  680. if (useCapture === undefined)
  681. useCapture = false;
  682. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  683. action = "DOMMouseScroll"; // For Firefox
  684. }
  685. element.addEventListener(action, listener, useCapture);
  686. } else {
  687. element.attachEvent("on" + action, listener); // IE browsers
  688. }
  689. };
  690. /**
  691. * Remove an event listener from an element
  692. * @param {Element} element An html dom element
  693. * @param {string} action The name of the event, for example "mousedown"
  694. * @param {function} listener The listener function
  695. * @param {boolean} [useCapture]
  696. */
  697. exports.removeEventListener = function (element, action, listener, useCapture) {
  698. if (element.removeEventListener) {
  699. // non-IE browsers
  700. if (useCapture === undefined)
  701. useCapture = false;
  702. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  703. action = "DOMMouseScroll"; // For Firefox
  704. }
  705. element.removeEventListener(action, listener, useCapture);
  706. } else {
  707. // IE browsers
  708. element.detachEvent("on" + action, listener);
  709. }
  710. };
  711. /**
  712. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  713. * @param {Event} event
  714. */
  715. exports.preventDefault = function (event) {
  716. if (!event)
  717. event = window.event;
  718. if (event.preventDefault) {
  719. event.preventDefault(); // non-IE browsers
  720. }
  721. else {
  722. event.returnValue = false; // IE browsers
  723. }
  724. };
  725. /**
  726. * Get HTML element which is the target of the event
  727. * @param {Event} event
  728. * @return {Element} target element
  729. */
  730. exports.getTarget = function (event) {
  731. // code from http://www.quirksmode.org/js/events_properties.html
  732. if (!event) {
  733. event = window.event;
  734. }
  735. var target;
  736. if (event.target) {
  737. target = event.target;
  738. }
  739. else if (event.srcElement) {
  740. target = event.srcElement;
  741. }
  742. if (target.nodeType != undefined && target.nodeType == 3) {
  743. // defeat Safari bug
  744. target = target.parentNode;
  745. }
  746. return target;
  747. };
  748. /**
  749. * Check if given element contains given parent somewhere in the DOM tree
  750. * @param {Element} element
  751. * @param {Element} parent
  752. * @returns {boolean}
  753. */
  754. exports.hasParent = function (element, parent) {
  755. var e = element;
  756. while (e) {
  757. if (e === parent) {
  758. return true;
  759. }
  760. e = e.parentNode;
  761. }
  762. return false;
  763. };
  764. exports.option = {};
  765. /**
  766. * Convert a value into a boolean
  767. * @param {Boolean | function | undefined} value
  768. * @param {Boolean} [defaultValue]
  769. * @returns {Boolean} bool
  770. */
  771. exports.option.asBoolean = function (value, defaultValue) {
  772. if (typeof value == 'function') {
  773. value = value();
  774. }
  775. if (value != null) {
  776. return (value != false);
  777. }
  778. return defaultValue || null;
  779. };
  780. /**
  781. * Convert a value into a number
  782. * @param {Boolean | function | undefined} value
  783. * @param {Number} [defaultValue]
  784. * @returns {Number} number
  785. */
  786. exports.option.asNumber = function (value, defaultValue) {
  787. if (typeof value == 'function') {
  788. value = value();
  789. }
  790. if (value != null) {
  791. return Number(value) || defaultValue || null;
  792. }
  793. return defaultValue || null;
  794. };
  795. /**
  796. * Convert a value into a string
  797. * @param {String | function | undefined} value
  798. * @param {String} [defaultValue]
  799. * @returns {String} str
  800. */
  801. exports.option.asString = function (value, defaultValue) {
  802. if (typeof value == 'function') {
  803. value = value();
  804. }
  805. if (value != null) {
  806. return String(value);
  807. }
  808. return defaultValue || null;
  809. };
  810. /**
  811. * Convert a size or location into a string with pixels or a percentage
  812. * @param {String | Number | function | undefined} value
  813. * @param {String} [defaultValue]
  814. * @returns {String} size
  815. */
  816. exports.option.asSize = function (value, defaultValue) {
  817. if (typeof value == 'function') {
  818. value = value();
  819. }
  820. if (exports.isString(value)) {
  821. return value;
  822. }
  823. else if (exports.isNumber(value)) {
  824. return value + 'px';
  825. }
  826. else {
  827. return defaultValue || null;
  828. }
  829. };
  830. /**
  831. * Convert a value into a DOM element
  832. * @param {HTMLElement | function | undefined} value
  833. * @param {HTMLElement} [defaultValue]
  834. * @returns {HTMLElement | null} dom
  835. */
  836. exports.option.asElement = function (value, defaultValue) {
  837. if (typeof value == 'function') {
  838. value = value();
  839. }
  840. return value || defaultValue || null;
  841. };
  842. /**
  843. * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
  844. *
  845. * @param {String} hex
  846. * @returns {{r: *, g: *, b: *}} | 255 range
  847. */
  848. exports.hexToRGB = function (hex) {
  849. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  850. var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  851. hex = hex.replace(shorthandRegex, function (m, r, g, b) {
  852. return r + r + g + g + b + b;
  853. });
  854. var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  855. return result ? {
  856. r: parseInt(result[1], 16),
  857. g: parseInt(result[2], 16),
  858. b: parseInt(result[3], 16)
  859. } : null;
  860. };
  861. /**
  862. * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string.
  863. * @param {String} color
  864. * @param {Number} opacity
  865. * @returns {String}
  866. */
  867. exports.overrideOpacity = function (color, opacity) {
  868. var rgb;
  869. if (color.indexOf("rgba") != -1) {
  870. return color;
  871. }
  872. else if (color.indexOf("rgb") != -1) {
  873. rgb = color.substr(color.indexOf("(") + 1).replace(")", "").split(",");
  874. return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + opacity + ")"
  875. }
  876. else {
  877. rgb = exports.hexToRGB(color);
  878. if (rgb == null) {
  879. return color;
  880. }
  881. else {
  882. return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + "," + opacity + ")"
  883. }
  884. }
  885. };
  886. /**
  887. *
  888. * @param {Number} red 0 -- 255
  889. * @param {Number} green 0 -- 255
  890. * @param {Number} blue 0 -- 255
  891. * @returns {String}
  892. * @constructor
  893. */
  894. exports.RGBToHex = function (red, green, blue) {
  895. return "#" + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
  896. };
  897. /**
  898. * Parse a color property into an object with border, background, and
  899. * highlight colors
  900. * @param {Object | String} color
  901. * @return {Object} colorObject
  902. */
  903. exports.parseColor = function (color) {
  904. var c;
  905. if (exports.isString(color) === true) {
  906. if (exports.isValidRGB(color) === true) {
  907. var rgb = color.substr(4).substr(0, color.length - 5).split(',').map(function (value) { return parseInt(value) });
  908. color = exports.RGBToHex(rgb[0], rgb[1], rgb[2]);
  909. }
  910. if (exports.isValidHex(color) === true) {
  911. var hsv = exports.hexToHSV(color);
  912. var lighterColorHSV = { h: hsv.h, s: hsv.s * 0.8, v: Math.min(1, hsv.v * 1.02) };
  913. var darkerColorHSV = { h: hsv.h, s: Math.min(1, hsv.s * 1.25), v: hsv.v * 0.8 };
  914. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h, darkerColorHSV.s, darkerColorHSV.v);
  915. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h, lighterColorHSV.s, lighterColorHSV.v);
  916. c = {
  917. background: color,
  918. border: darkerColorHex,
  919. highlight: {
  920. background: lighterColorHex,
  921. border: darkerColorHex
  922. },
  923. hover: {
  924. background: lighterColorHex,
  925. border: darkerColorHex
  926. }
  927. };
  928. }
  929. else {
  930. c = {
  931. background: color,
  932. border: color,
  933. highlight: {
  934. background: color,
  935. border: color
  936. },
  937. hover: {
  938. background: color,
  939. border: color
  940. }
  941. };
  942. }
  943. }
  944. else {
  945. c = {};
  946. c.background = color.background || undefined;
  947. c.border = color.border || undefined;
  948. if (exports.isString(color.highlight)) {
  949. c.highlight = {
  950. border: color.highlight,
  951. background: color.highlight
  952. }
  953. }
  954. else {
  955. c.highlight = {};
  956. c.highlight.background = color.highlight && color.highlight.background || undefined;
  957. c.highlight.border = color.highlight && color.highlight.border || undefined;
  958. }
  959. if (exports.isString(color.hover)) {
  960. c.hover = {
  961. border: color.hover,
  962. background: color.hover
  963. }
  964. }
  965. else {
  966. c.hover = {};
  967. c.hover.background = color.hover && color.hover.background || undefined;
  968. c.hover.border = color.hover && color.hover.border || undefined;
  969. }
  970. }
  971. return c;
  972. };
  973. /**
  974. * http://www.javascripter.net/faq/rgb2hsv.htm
  975. *
  976. * @param {Number} red
  977. * @param {Number} green
  978. * @param {Number} blue
  979. * @returns {{h: Number, s: Number, v: Number}}
  980. * @constructor
  981. */
  982. exports.RGBToHSV = function (red, green, blue) {
  983. red = red / 255; green = green / 255; blue = blue / 255;
  984. var minRGB = Math.min(red, Math.min(green, blue));
  985. var maxRGB = Math.max(red, Math.max(green, blue));
  986. // Black-gray-white
  987. if (minRGB == maxRGB) {
  988. return { h: 0, s: 0, v: minRGB };
  989. }
  990. // Colors other than black-gray-white:
  991. var d = (red == minRGB) ? green - blue : ((blue == minRGB) ? red - green : blue - red);
  992. var h = (red == minRGB) ? 3 : ((blue == minRGB) ? 1 : 5);
  993. var hue = 60 * (h - d / (maxRGB - minRGB)) / 360;
  994. var saturation = (maxRGB - minRGB) / maxRGB;
  995. var value = maxRGB;
  996. return { h: hue, s: saturation, v: value };
  997. };
  998. var cssUtil = {
  999. // split a string with css styles into an object with key/values
  1000. split: function (cssText) {
  1001. var styles = {};
  1002. cssText.split(';').forEach(function (style) {
  1003. if (style.trim() != '') {
  1004. var parts = style.split(':');
  1005. var key = parts[0].trim();
  1006. var value = parts[1].trim();
  1007. styles[key] = value;
  1008. }
  1009. });
  1010. return styles;
  1011. },
  1012. // build a css text string from an object with key/values
  1013. join: function (styles) {
  1014. return Object.keys(styles)
  1015. .map(function (key) {
  1016. return key + ': ' + styles[key];
  1017. })
  1018. .join('; ');
  1019. }
  1020. };
  1021. /**
  1022. * Append a string with css styles to an element
  1023. * @param {Element} element
  1024. * @param {String} cssText
  1025. */
  1026. exports.addCssText = function (element, cssText) {
  1027. var currentStyles = cssUtil.split(element.style.cssText);
  1028. var newStyles = cssUtil.split(cssText);
  1029. var styles = exports.extend(currentStyles, newStyles);
  1030. element.style.cssText = cssUtil.join(styles);
  1031. };
  1032. /**
  1033. * Remove a string with css styles from an element
  1034. * @param {Element} element
  1035. * @param {String} cssText
  1036. */
  1037. exports.removeCssText = function (element, cssText) {
  1038. var styles = cssUtil.split(element.style.cssText);
  1039. var removeStyles = cssUtil.split(cssText);
  1040. for (var key in removeStyles) {
  1041. if (removeStyles.hasOwnProperty(key)) {
  1042. delete styles[key];
  1043. }
  1044. }
  1045. element.style.cssText = cssUtil.join(styles);
  1046. };
  1047. /**
  1048. * https://gist.github.com/mjijackson/5311256
  1049. * @param {Number} h
  1050. * @param {Number} s
  1051. * @param {Number} v
  1052. * @returns {{r: number, g: number, b: number}}
  1053. * @constructor
  1054. */
  1055. exports.HSVToRGB = function (h, s, v) {
  1056. var r, g, b;
  1057. var i = Math.floor(h * 6);
  1058. var f = h * 6 - i;
  1059. var p = v * (1 - s);
  1060. var q = v * (1 - f * s);
  1061. var t = v * (1 - (1 - f) * s);
  1062. switch (i % 6) {
  1063. case 0: r = v, g = t, b = p; break;
  1064. case 1: r = q, g = v, b = p; break;
  1065. case 2: r = p, g = v, b = t; break;
  1066. case 3: r = p, g = q, b = v; break;
  1067. case 4: r = t, g = p, b = v; break;
  1068. case 5: r = v, g = p, b = q; break;
  1069. }
  1070. return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255) };
  1071. };
  1072. exports.HSVToHex = function (h, s, v) {
  1073. var rgb = exports.HSVToRGB(h, s, v);
  1074. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1075. };
  1076. exports.hexToHSV = function (hex) {
  1077. var rgb = exports.hexToRGB(hex);
  1078. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1079. };
  1080. exports.isValidHex = function (hex) {
  1081. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1082. return isOk;
  1083. };
  1084. exports.isValidRGB = function (rgb) {
  1085. rgb = rgb.replace(" ", "");
  1086. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1087. return isOk;
  1088. };
  1089. exports.isValidRGBA = function (rgba) {
  1090. rgba = rgba.replace(" ", "");
  1091. var isOk = /rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(rgba);
  1092. return isOk;
  1093. };
  1094. /**
  1095. * This recursively redirects the prototype of JSON objects to the referenceObject
  1096. * This is used for default options.
  1097. *
  1098. * @param {Array<String>} fields
  1099. * @param {Object} referenceObject
  1100. * @returns {*}
  1101. */
  1102. exports.selectiveBridgeObject = function (fields, referenceObject) {
  1103. if (typeof referenceObject == "object") {
  1104. var objectTo = Object.create(referenceObject);
  1105. for (var i = 0; i < fields.length; i++) {
  1106. if (referenceObject.hasOwnProperty(fields[i])) {
  1107. if (typeof referenceObject[fields[i]] == "object") {
  1108. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1109. }
  1110. }
  1111. }
  1112. return objectTo;
  1113. }
  1114. else {
  1115. return null;
  1116. }
  1117. };
  1118. /**
  1119. * This recursively redirects the prototype of JSON objects to the referenceObject
  1120. * This is used for default options.
  1121. *
  1122. * @param {Object} referenceObject
  1123. * @returns {*}
  1124. */
  1125. exports.bridgeObject = function (referenceObject) {
  1126. if (typeof referenceObject == "object") {
  1127. var objectTo = Object.create(referenceObject);
  1128. if (referenceObject instanceof Element) {
  1129. // Avoid bridging DOM objects
  1130. objectTo = referenceObject;
  1131. } else {
  1132. objectTo = Object.create(referenceObject);
  1133. for (var i in referenceObject) {
  1134. if (referenceObject.hasOwnProperty(i)) {
  1135. if (typeof referenceObject[i] == "object") {
  1136. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1137. }
  1138. }
  1139. }
  1140. }
  1141. return objectTo;
  1142. }
  1143. else {
  1144. return null;
  1145. }
  1146. };
  1147. /**
  1148. * This method provides a stable sort implementation, very fast for presorted data
  1149. *
  1150. * @param {Array} a the array
  1151. * @param {function} compare an order comparator
  1152. * @returns {Array}
  1153. */
  1154. exports.insertSort = function (a,compare) {
  1155. for (var i = 0; i < a.length; i++) {
  1156. var k = a[i];
  1157. for (var j = i; j > 0 && compare(k,a[j - 1])<0; j--) {
  1158. a[j] = a[j - 1];
  1159. }
  1160. a[j] = k;
  1161. }
  1162. return a;
  1163. }
  1164. /**
  1165. * This is used to set the options of subobjects in the options object.
  1166. *
  1167. * A requirement of these subobjects is that they have an 'enabled' element
  1168. * which is optional for the user but mandatory for the program.
  1169. *
  1170. * The added value here of the merge is that option 'enabled' is set as required.
  1171. *
  1172. *
  1173. * @param {object} mergeTarget | either this.options or the options used for the groups.
  1174. * @param {object} options | options
  1175. * @param {String} option | option key in the options argument
  1176. * @param {object} globalOptions | global options, passed in to determine value of option 'enabled'
  1177. */
  1178. exports.mergeOptions = function (mergeTarget, options, option, globalOptions = {}) {
  1179. // Local helpers
  1180. var isPresent = function(obj) {
  1181. return obj !== null && obj !== undefined;
  1182. }
  1183. var isObject = function(obj) {
  1184. return obj !== null && typeof obj === 'object';
  1185. }
  1186. // https://stackoverflow.com/a/34491287/1223531
  1187. var isEmpty = function(obj) {
  1188. for (var x in obj) { if (obj.hasOwnProperty(x)) return false; }
  1189. return true;
  1190. };
  1191. // Guards
  1192. if (!isObject(mergeTarget)) {
  1193. throw new Error('Parameter mergeTarget must be an object');
  1194. }
  1195. if (!isObject(options)) {
  1196. throw new Error('Parameter options must be an object');
  1197. }
  1198. if (!isPresent(option)) {
  1199. throw new Error('Parameter option must have a value');
  1200. }
  1201. if (!isObject(globalOptions)) {
  1202. throw new Error('Parameter globalOptions must be an object');
  1203. }
  1204. //
  1205. // Actual merge routine, separated from main logic
  1206. // Only a single level of options is merged. Deeper levels are ref'd. This may actually be an issue.
  1207. //
  1208. var doMerge = function(target, options, option) {
  1209. if (!isObject(target[option])) {
  1210. target[option] = {};
  1211. }
  1212. let src = options[option];
  1213. let dst = target[option];
  1214. for (var prop in src) {
  1215. if (src.hasOwnProperty(prop)) {
  1216. dst[prop] = src[prop];
  1217. }
  1218. }
  1219. };
  1220. // Local initialization
  1221. var srcOption = options[option];
  1222. var globalPassed = isObject(globalOptions) && !isEmpty(globalOptions);
  1223. var globalOption = globalPassed? globalOptions[option]: undefined;
  1224. var globalEnabled = globalOption? globalOption.enabled: undefined;
  1225. /////////////////////////////////////////
  1226. // Main routine
  1227. /////////////////////////////////////////
  1228. if (srcOption === undefined) {
  1229. return; // Nothing to do
  1230. }
  1231. if ((typeof srcOption) === 'boolean') {
  1232. if (!isObject(mergeTarget[option])) {
  1233. mergeTarget[option] = {};
  1234. }
  1235. mergeTarget[option].enabled = srcOption;
  1236. return;
  1237. }
  1238. if (srcOption === null && !isObject(mergeTarget[option])) {
  1239. // If possible, explicit copy from globals
  1240. if (isPresent(globalOption)) {
  1241. mergeTarget[option] = Object.create(globalOption);
  1242. } else {
  1243. return; // Nothing to do
  1244. }
  1245. }
  1246. if (!isObject(srcOption)) {
  1247. return;
  1248. }
  1249. //
  1250. // Ensure that 'enabled' is properly set. It is required internally
  1251. // Note that the value from options will always overwrite the existing value
  1252. //
  1253. let enabled = true; // default value
  1254. if (srcOption.enabled !== undefined) {
  1255. enabled = srcOption.enabled;
  1256. } else {
  1257. // Take from globals, if present
  1258. if (globalEnabled !== undefined) {
  1259. enabled = globalOption.enabled;
  1260. }
  1261. }
  1262. doMerge(mergeTarget, options, option);
  1263. mergeTarget[option].enabled = enabled;
  1264. }
  1265. /**
  1266. * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
  1267. * this function will then iterate in both directions over this sorted list to find all visible items.
  1268. *
  1269. * @param {Item[]} orderedItems | Items ordered by start
  1270. * @param {function} comparator | -1 is lower, 0 is equal, 1 is higher
  1271. * @param {String} field
  1272. * @param {String} field2
  1273. * @returns {number}
  1274. * @private
  1275. */
  1276. exports.binarySearchCustom = function (orderedItems, comparator, field, field2) {
  1277. var maxIterations = 10000;
  1278. var iteration = 0;
  1279. var low = 0;
  1280. var high = orderedItems.length - 1;
  1281. while (low <= high && iteration < maxIterations) {
  1282. var middle = Math.floor((low + high) / 2);
  1283. var item = orderedItems[middle];
  1284. var value = (field2 === undefined) ? item[field] : item[field][field2];
  1285. var searchResult = comparator(value);
  1286. if (searchResult == 0) { // jihaa, found a visible item!
  1287. return middle;
  1288. }
  1289. else if (searchResult == -1) { // it is too small --> increase low
  1290. low = middle + 1;
  1291. }
  1292. else { // it is too big --> decrease high
  1293. high = middle - 1;
  1294. }
  1295. iteration++;
  1296. }
  1297. return -1;
  1298. };
  1299. /**
  1300. * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
  1301. * two values, we return either the one before or the one after, depending on user input
  1302. * If it is found, we return the index, else -1.
  1303. *
  1304. * @param {Array} orderedItems
  1305. * @param {{start: number, end: number}} target
  1306. * @param {String} field
  1307. * @param {String} sidePreference 'before' or 'after'
  1308. * @param {function} comparator an optional comparator, returning -1,0,1 for <,==,>.
  1309. * @returns {number}
  1310. * @private
  1311. */
  1312. exports.binarySearchValue = function (orderedItems, target, field, sidePreference, comparator) {
  1313. var maxIterations = 10000;
  1314. var iteration = 0;
  1315. var low = 0;
  1316. var high = orderedItems.length - 1;
  1317. var prevValue, value, nextValue, middle;
  1318. comparator = comparator != undefined ? comparator : function (a, b) {
  1319. return a == b ? 0 : a < b ? -1 : 1
  1320. };
  1321. while (low <= high && iteration < maxIterations) {
  1322. // get a new guess
  1323. middle = Math.floor(0.5 * (high + low));
  1324. prevValue = orderedItems[Math.max(0, middle - 1)][field];
  1325. value = orderedItems[middle][field];
  1326. nextValue = orderedItems[Math.min(orderedItems.length - 1, middle + 1)][field];
  1327. if (comparator(value, target) == 0) { // we found the target
  1328. return middle;
  1329. }
  1330. else if (comparator(prevValue, target) < 0 && comparator(value, target) > 0) { // target is in between of the previous and the current
  1331. return sidePreference == 'before' ? Math.max(0, middle - 1) : middle;
  1332. }
  1333. else if (comparator(value, target) < 0 && comparator(nextValue, target) > 0) { // target is in between of the current and the next
  1334. return sidePreference == 'before' ? middle : Math.min(orderedItems.length - 1, middle + 1);
  1335. }
  1336. else { // didnt find the target, we need to change our boundaries.
  1337. if (comparator(value, target) < 0) { // it is too small --> increase low
  1338. low = middle + 1;
  1339. }
  1340. else { // it is too big --> decrease high
  1341. high = middle - 1;
  1342. }
  1343. }
  1344. iteration++;
  1345. }
  1346. // didnt find anything. Return -1.
  1347. return -1;
  1348. };
  1349. /*
  1350. * Easing Functions - inspired from http://gizma.com/easing/
  1351. * only considering the t value for the range [0, 1] => [0, 1]
  1352. * https://gist.github.com/gre/1650294
  1353. */
  1354. exports.easingFunctions = {
  1355. // no easing, no acceleration
  1356. linear: function (t) {
  1357. return t
  1358. },
  1359. // accelerating from zero velocity
  1360. easeInQuad: function (t) {
  1361. return t * t
  1362. },
  1363. // decelerating to zero velocity
  1364. easeOutQuad: function (t) {
  1365. return t * (2 - t)
  1366. },
  1367. // acceleration until halfway, then deceleration
  1368. easeInOutQuad: function (t) {
  1369. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  1370. },
  1371. // accelerating from zero velocity
  1372. easeInCubic: function (t) {
  1373. return t * t * t
  1374. },
  1375. // decelerating to zero velocity
  1376. easeOutCubic: function (t) {
  1377. return (--t) * t * t + 1
  1378. },
  1379. // acceleration until halfway, then deceleration
  1380. easeInOutCubic: function (t) {
  1381. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  1382. },
  1383. // accelerating from zero velocity
  1384. easeInQuart: function (t) {
  1385. return t * t * t * t
  1386. },
  1387. // decelerating to zero velocity
  1388. easeOutQuart: function (t) {
  1389. return 1 - (--t) * t * t * t
  1390. },
  1391. // acceleration until halfway, then deceleration
  1392. easeInOutQuart: function (t) {
  1393. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  1394. },
  1395. // accelerating from zero velocity
  1396. easeInQuint: function (t) {
  1397. return t * t * t * t * t
  1398. },
  1399. // decelerating to zero velocity
  1400. easeOutQuint: function (t) {
  1401. return 1 + (--t) * t * t * t * t
  1402. },
  1403. // acceleration until halfway, then deceleration
  1404. easeInOutQuint: function (t) {
  1405. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  1406. }
  1407. };
  1408. exports.getScrollBarWidth = function () {
  1409. var inner = document.createElement('p');
  1410. inner.style.width = "100%";
  1411. inner.style.height = "200px";
  1412. var outer = document.createElement('div');
  1413. outer.style.position = "absolute";
  1414. outer.style.top = "0px";
  1415. outer.style.left = "0px";
  1416. outer.style.visibility = "hidden";
  1417. outer.style.width = "200px";
  1418. outer.style.height = "150px";
  1419. outer.style.overflow = "hidden";
  1420. outer.appendChild (inner);
  1421. document.body.appendChild (outer);
  1422. var w1 = inner.offsetWidth;
  1423. outer.style.overflow = 'scroll';
  1424. var w2 = inner.offsetWidth;
  1425. if (w1 == w2) w2 = outer.clientWidth;
  1426. document.body.removeChild (outer);
  1427. return (w1 - w2);
  1428. };
  1429. exports.topMost = function (pile, accessors) {
  1430. let candidate;
  1431. if (!Array.isArray(accessors)) {
  1432. accessors = [accessors];
  1433. }
  1434. for (const member of pile) {
  1435. if (member) {
  1436. candidate = member[accessors[0]];
  1437. for (let i = 1; i < accessors.length; i++){
  1438. if (candidate) {
  1439. candidate = candidate[accessors[i]]
  1440. } else {
  1441. continue;
  1442. }
  1443. }
  1444. if (typeof candidate != 'undefined') {
  1445. break;
  1446. }
  1447. }
  1448. }
  1449. return candidate;
  1450. };