97039a3a0812bcb68f0299c7bf1d2c1cf50216fe56a20e7660d987495895ea7b71ec8b33034aa9bf7807fadef53086f997336aba79c53ae2ee2639df5a152e 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999
  1. import {isIE8, isIE9, isSafari} from '../browser';
  2. import {hasCaptionProblem} from '../feature';
  3. /**
  4. * Get the parent of the specified node in the DOM tree.
  5. *
  6. * @param {HTMLElement} element Element from which traversing is started.
  7. * @param {Number} [level=0] Traversing deep level.
  8. * @return {HTMLElement|null}
  9. */
  10. export function getParent(element, level = 0) {
  11. let iteration = -1;
  12. let parent = null;
  13. while (element != null) {
  14. if (iteration === level) {
  15. parent = element;
  16. break;
  17. }
  18. if (element.host && element.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  19. element = element.host;
  20. } else {
  21. iteration++;
  22. element = element.parentNode;
  23. }
  24. }
  25. return parent;
  26. }
  27. /**
  28. * Goes up the DOM tree (including given element) until it finds an element that matches the nodes or nodes name.
  29. * This method goes up through web components.
  30. *
  31. * @param {HTMLElement} element Element from which traversing is started
  32. * @param {Array} nodes Array of elements or Array of elements name
  33. * @param {HTMLElement} [until]
  34. * @returns {HTMLElement|null}
  35. */
  36. export function closest(element, nodes, until) {
  37. while (element != null && element !== until) {
  38. if (element.nodeType === Node.ELEMENT_NODE && (nodes.indexOf(element.nodeName) > -1 || nodes.indexOf(element) > -1)) {
  39. return element;
  40. }
  41. if (element.host && element.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  42. element = element.host;
  43. } else {
  44. element = element.parentNode;
  45. }
  46. }
  47. return null;
  48. }
  49. /**
  50. * Goes "down" the DOM tree (including given element) until it finds an element that matches the nodes or nodes name.
  51. *
  52. * @param {HTMLElement} element Element from which traversing is started
  53. * @param {Array} nodes Array of elements or Array of elements name
  54. * @param {HTMLElement} [until]
  55. * @returns {HTMLElement|null}
  56. */
  57. export function closestDown(element, nodes, until) {
  58. const matched = [];
  59. while (element) {
  60. element = closest(element, nodes, until);
  61. if (!element || (until && !until.contains(element))) {
  62. break;
  63. }
  64. matched.push(element);
  65. if (element.host && element.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  66. element = element.host;
  67. } else {
  68. element = element.parentNode;
  69. }
  70. }
  71. const length = matched.length;
  72. return length ? matched[length - 1] : null;
  73. }
  74. /**
  75. * Goes up the DOM tree and checks if element is child of another element.
  76. *
  77. * @param child Child element
  78. * @param {Object|String} parent Parent element OR selector of the parent element.
  79. * If string provided, function returns `true` for the first occurrence of element with that class.
  80. * @returns {Boolean}
  81. */
  82. export function isChildOf(child, parent) {
  83. var node = child.parentNode;
  84. var queriedParents = [];
  85. if (typeof parent === 'string') {
  86. queriedParents = Array.prototype.slice.call(document.querySelectorAll(parent), 0);
  87. } else {
  88. queriedParents.push(parent);
  89. }
  90. while (node != null) {
  91. if (queriedParents.indexOf(node) > -1) {
  92. return true;
  93. }
  94. node = node.parentNode;
  95. }
  96. return false;
  97. }
  98. /**
  99. * Check if an element is part of `hot-table` web component.
  100. *
  101. * @param {Element} element
  102. * @returns {Boolean}
  103. */
  104. export function isChildOfWebComponentTable(element) {
  105. var hotTableName = 'hot-table',
  106. result = false,
  107. parentNode;
  108. parentNode = polymerWrap(element);
  109. function isHotTable(element) {
  110. return element.nodeType === Node.ELEMENT_NODE && element.nodeName === hotTableName.toUpperCase();
  111. }
  112. while (parentNode != null) {
  113. if (isHotTable(parentNode)) {
  114. result = true;
  115. break;
  116. } else if (parentNode.host && parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  117. result = isHotTable(parentNode.host);
  118. if (result) {
  119. break;
  120. }
  121. parentNode = parentNode.host;
  122. }
  123. parentNode = parentNode.parentNode;
  124. }
  125. return result;
  126. }
  127. /**
  128. * Wrap element into polymer/webcomponent container if exists
  129. *
  130. * @param element
  131. * @returns {*}
  132. */
  133. export function polymerWrap(element) {
  134. /* global Polymer */
  135. return typeof Polymer !== 'undefined' && typeof wrap === 'function' ? wrap(element) : element;
  136. }
  137. /**
  138. * Unwrap element from polymer/webcomponent container if exists
  139. *
  140. * @param element
  141. * @returns {*}
  142. */
  143. export function polymerUnwrap(element) {
  144. /* global Polymer */
  145. return typeof Polymer !== 'undefined' && typeof unwrap === 'function' ? unwrap(element) : element;
  146. }
  147. /**
  148. * Counts index of element within its parent
  149. * WARNING: for performance reasons, assumes there are only element nodes (no text nodes). This is true for Walkotnable
  150. * Otherwise would need to check for nodeType or use previousElementSibling
  151. *
  152. * @see http://jsperf.com/sibling-index/10
  153. * @param {Element} element
  154. * @return {Number}
  155. */
  156. export function index(element) {
  157. var i = 0;
  158. if (element.previousSibling) {
  159. /* eslint-disable no-cond-assign */
  160. while (element = element.previousSibling) {
  161. ++i;
  162. }
  163. }
  164. return i;
  165. }
  166. /**
  167. * Check if the provided overlay contains the provided element
  168. *
  169. * @param {String} overlay
  170. * @param {HTMLElement} element
  171. * @returns {boolean}
  172. */
  173. export function overlayContainsElement(overlayType, element) {
  174. let overlayElement = document.querySelector(`.ht_clone_${overlayType}`);
  175. return overlayElement ? overlayElement.contains(element) : null;
  176. }
  177. var classListSupport = !!document.documentElement.classList;
  178. var _hasClass,
  179. _addClass,
  180. _removeClass;
  181. function filterEmptyClassNames(classNames) {
  182. var len = 0,
  183. result = [];
  184. if (!classNames || !classNames.length) {
  185. return result;
  186. }
  187. while (classNames[len]) {
  188. result.push(classNames[len]);
  189. len++;
  190. }
  191. return result;
  192. }
  193. if (classListSupport) {
  194. var isSupportMultipleClassesArg = (function() {
  195. var element = document.createElement('div');
  196. element.classList.add('test', 'test2');
  197. return element.classList.contains('test2');
  198. }());
  199. _hasClass = function _hasClass(element, className) {
  200. if (className === '') {
  201. return false;
  202. }
  203. return element.classList.contains(className);
  204. };
  205. _addClass = function _addClass(element, className) {
  206. var len = 0;
  207. if (typeof className === 'string') {
  208. className = className.split(' ');
  209. }
  210. className = filterEmptyClassNames(className);
  211. if (isSupportMultipleClassesArg) {
  212. element.classList.add.apply(element.classList, className);
  213. } else {
  214. while (className && className[len]) {
  215. element.classList.add(className[len]);
  216. len++;
  217. }
  218. }
  219. };
  220. _removeClass = function _removeClass(element, className) {
  221. var len = 0;
  222. if (typeof className === 'string') {
  223. className = className.split(' ');
  224. }
  225. className = filterEmptyClassNames(className);
  226. if (isSupportMultipleClassesArg) {
  227. element.classList.remove.apply(element.classList, className);
  228. } else {
  229. while (className && className[len]) {
  230. element.classList.remove(className[len]);
  231. len++;
  232. }
  233. }
  234. };
  235. } else {
  236. var createClassNameRegExp = function createClassNameRegExp(className) {
  237. return new RegExp(`(\\s|^)${className}(\\s|$)`);
  238. };
  239. _hasClass = function _hasClass(element, className) {
  240. // http://snipplr.com/view/3561/addclass-removeclass-hasclass/
  241. return !!element.className.match(createClassNameRegExp(className));
  242. };
  243. _addClass = function _addClass(element, className) {
  244. var len = 0,
  245. _className = element.className;
  246. if (typeof className === 'string') {
  247. className = className.split(' ');
  248. }
  249. if (_className === '') {
  250. _className = className.join(' ');
  251. } else {
  252. while (className && className[len]) {
  253. if (!createClassNameRegExp(className[len]).test(_className)) {
  254. _className += ` ${className[len]}`;
  255. }
  256. len++;
  257. }
  258. }
  259. element.className = _className;
  260. };
  261. _removeClass = function _removeClass(element, className) {
  262. var len = 0,
  263. _className = element.className;
  264. if (typeof className === 'string') {
  265. className = className.split(' ');
  266. }
  267. while (className && className[len]) {
  268. // String.prototype.trim is defined in polyfill.js
  269. _className = _className.replace(createClassNameRegExp(className[len]), ' ').trim();
  270. len++;
  271. }
  272. if (element.className !== _className) {
  273. element.className = _className;
  274. }
  275. };
  276. }
  277. /**
  278. * Checks if element has class name
  279. *
  280. * @param {HTMLElement} element
  281. * @param {String} className Class name to check
  282. * @returns {Boolean}
  283. */
  284. export function hasClass(element, className) {
  285. return _hasClass(element, className);
  286. }
  287. /**
  288. * Add class name to an element
  289. *
  290. * @param {HTMLElement} element
  291. * @param {String|Array} className Class name as string or array of strings
  292. */
  293. export function addClass(element, className) {
  294. return _addClass(element, className);
  295. }
  296. /**
  297. * Remove class name from an element
  298. *
  299. * @param {HTMLElement} element
  300. * @param {String|Array} className Class name as string or array of strings
  301. */
  302. export function removeClass(element, className) {
  303. return _removeClass(element, className);
  304. }
  305. export function removeTextNodes(element, parent) {
  306. if (element.nodeType === 3) {
  307. parent.removeChild(element); // bye text nodes!
  308. } else if (['TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR'].indexOf(element.nodeName) > -1) {
  309. var childs = element.childNodes;
  310. for (var i = childs.length - 1; i >= 0; i--) {
  311. removeTextNodes(childs[i], element);
  312. }
  313. }
  314. }
  315. /**
  316. * Remove childs function
  317. * WARNING - this doesn't unload events and data attached by jQuery
  318. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/9
  319. * http://jsperf.com/jquery-html-vs-empty-vs-innerhtml/11 - no siginificant improvement with Chrome remove() method
  320. *
  321. * @param element
  322. * @returns {void}
  323. */
  324. //
  325. export function empty(element) {
  326. var child;
  327. /* eslint-disable no-cond-assign */
  328. while (child = element.lastChild) {
  329. element.removeChild(child);
  330. }
  331. }
  332. export const HTML_CHARACTERS = /(<(.*)>|&(.*);)/;
  333. /**
  334. * Insert content into element trying avoid innerHTML method.
  335. * @return {void}
  336. */
  337. export function fastInnerHTML(element, content) {
  338. if (HTML_CHARACTERS.test(content)) {
  339. element.innerHTML = content;
  340. } else {
  341. fastInnerText(element, content);
  342. }
  343. }
  344. /**
  345. * Insert text content into element
  346. * @return {void}
  347. */
  348. var textContextSupport = !!document.createTextNode('test').textContent;
  349. export function fastInnerText(element, content) {
  350. var child = element.firstChild;
  351. if (child && child.nodeType === 3 && child.nextSibling === null) {
  352. // fast lane - replace existing text node
  353. if (textContextSupport) {
  354. // http://jsperf.com/replace-text-vs-reuse
  355. child.textContent = content;
  356. } else {
  357. // http://jsperf.com/replace-text-vs-reuse
  358. child.data = content;
  359. }
  360. } else {
  361. // slow lane - empty element and insert a text node
  362. empty(element);
  363. element.appendChild(document.createTextNode(content));
  364. }
  365. }
  366. /**
  367. * Returns true if element is attached to the DOM and visible, false otherwise
  368. * @param elem
  369. * @returns {boolean}
  370. */
  371. export function isVisible(elem) {
  372. var next = elem;
  373. while (polymerUnwrap(next) !== document.documentElement) { // until <html> reached
  374. if (next === null) { // parent detached from DOM
  375. return false;
  376. } else if (next.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
  377. if (next.host) { // this is Web Components Shadow DOM
  378. // see: http://w3c.github.io/webcomponents/spec/shadow/#encapsulation
  379. // according to spec, should be if (next.ownerDocument !== window.document), but that doesn't work yet
  380. if (next.host.impl) { // Chrome 33.0.1723.0 canary (2013-11-29) Web Platform features disabled
  381. return isVisible(next.host.impl);
  382. } else if (next.host) { // Chrome 33.0.1723.0 canary (2013-11-29) Web Platform features enabled
  383. return isVisible(next.host);
  384. }
  385. throw new Error('Lost in Web Components world');
  386. } else {
  387. return false; // this is a node detached from document in IE8
  388. }
  389. } else if (next.style.display === 'none') {
  390. return false;
  391. }
  392. next = next.parentNode;
  393. }
  394. return true;
  395. }
  396. /**
  397. * Returns elements top and left offset relative to the document. Function is not compatible with jQuery offset.
  398. *
  399. * @param {HTMLElement} elem
  400. * @return {Object} Returns object with `top` and `left` props
  401. */
  402. export function offset(elem) {
  403. var offsetLeft,
  404. offsetTop,
  405. lastElem,
  406. docElem,
  407. box;
  408. docElem = document.documentElement;
  409. if (hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  410. // fixes problem with Firefox ignoring <caption> in TABLE offset (see also export outerHeight)
  411. // http://jsperf.com/offset-vs-getboundingclientrect/8
  412. box = elem.getBoundingClientRect();
  413. return {
  414. top: box.top + (window.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0),
  415. left: box.left + (window.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)
  416. };
  417. }
  418. offsetLeft = elem.offsetLeft;
  419. offsetTop = elem.offsetTop;
  420. lastElem = elem;
  421. /* eslint-disable no-cond-assign */
  422. while (elem = elem.offsetParent) {
  423. // from my observation, document.body always has scrollLeft/scrollTop == 0
  424. if (elem === document.body) {
  425. break;
  426. }
  427. offsetLeft += elem.offsetLeft;
  428. offsetTop += elem.offsetTop;
  429. lastElem = elem;
  430. }
  431. // slow - http://jsperf.com/offset-vs-getboundingclientrect/6
  432. if (lastElem && lastElem.style.position === 'fixed') {
  433. // if(lastElem !== document.body) { //faster but does gives false positive in Firefox
  434. offsetLeft += window.pageXOffset || docElem.scrollLeft;
  435. offsetTop += window.pageYOffset || docElem.scrollTop;
  436. }
  437. return {
  438. left: offsetLeft,
  439. top: offsetTop
  440. };
  441. }
  442. /**
  443. * Returns the document's scrollTop property.
  444. *
  445. * @returns {Number}
  446. */
  447. export function getWindowScrollTop() {
  448. var res = window.scrollY;
  449. if (res === void 0) { // IE8-11
  450. res = document.documentElement.scrollTop;
  451. }
  452. return res;
  453. }
  454. /**
  455. * Returns the document's scrollLeft property.
  456. *
  457. * @returns {Number}
  458. */
  459. export function getWindowScrollLeft() {
  460. var res = window.scrollX;
  461. if (res === void 0) { // IE8-11
  462. res = document.documentElement.scrollLeft;
  463. }
  464. return res;
  465. }
  466. /**
  467. * Returns the provided element's scrollTop property.
  468. *
  469. * @param element
  470. * @returns {Number}
  471. */
  472. export function getScrollTop(element) {
  473. if (element === window) {
  474. return getWindowScrollTop();
  475. }
  476. return element.scrollTop;
  477. }
  478. /**
  479. * Returns the provided element's scrollLeft property.
  480. *
  481. * @param element
  482. * @returns {Number}
  483. */
  484. export function getScrollLeft(element) {
  485. if (element === window) {
  486. return getWindowScrollLeft();
  487. }
  488. return element.scrollLeft;
  489. }
  490. /**
  491. * Returns a DOM element responsible for scrolling of the provided element.
  492. *
  493. * @param {HTMLElement} element
  494. * @returns {HTMLElement} Element's scrollable parent
  495. */
  496. export function getScrollableElement(element) {
  497. var el = element.parentNode,
  498. props = ['auto', 'scroll'],
  499. overflow,
  500. overflowX,
  501. overflowY,
  502. computedStyle = '',
  503. computedOverflow = '',
  504. computedOverflowY = '',
  505. computedOverflowX = '';
  506. while (el && el.style && document.body !== el) {
  507. overflow = el.style.overflow;
  508. overflowX = el.style.overflowX;
  509. overflowY = el.style.overflowY;
  510. if (overflow == 'scroll' || overflowX == 'scroll' || overflowY == 'scroll') {
  511. return el;
  512. } else if (window.getComputedStyle) {
  513. computedStyle = window.getComputedStyle(el);
  514. computedOverflow = computedStyle.getPropertyValue('overflow');
  515. computedOverflowY = computedStyle.getPropertyValue('overflow-y');
  516. computedOverflowX = computedStyle.getPropertyValue('overflow-x');
  517. if (computedOverflow === 'scroll' || computedOverflowX === 'scroll' || computedOverflowY === 'scroll') {
  518. return el;
  519. }
  520. }
  521. if (el.clientHeight <= el.scrollHeight && (props.indexOf(overflowY) !== -1 || props.indexOf(overflow) !== -1 ||
  522. props.indexOf(computedOverflow) !== -1 || props.indexOf(computedOverflowY) !== -1)) {
  523. return el;
  524. }
  525. if (el.clientWidth <= el.scrollWidth && (props.indexOf(overflowX) !== -1 || props.indexOf(overflow) !== -1 ||
  526. props.indexOf(computedOverflow) !== -1 || props.indexOf(computedOverflowX) !== -1)) {
  527. return el;
  528. }
  529. el = el.parentNode;
  530. }
  531. return window;
  532. }
  533. /**
  534. * Returns a DOM element responsible for trimming the provided element.
  535. *
  536. * @param {HTMLElement} base Base element
  537. * @returns {HTMLElement} Base element's trimming parent
  538. */
  539. export function getTrimmingContainer(base) {
  540. var el = base.parentNode;
  541. while (el && el.style && document.body !== el) {
  542. if (el.style.overflow !== 'visible' && el.style.overflow !== '') {
  543. return el;
  544. } else if (window.getComputedStyle) {
  545. var computedStyle = window.getComputedStyle(el);
  546. if (computedStyle.getPropertyValue('overflow') !== 'visible' && computedStyle.getPropertyValue('overflow') !== '') {
  547. return el;
  548. }
  549. }
  550. el = el.parentNode;
  551. }
  552. return window;
  553. }
  554. /**
  555. * Returns a style property for the provided element. (Be it an inline or external style).
  556. *
  557. * @param {HTMLElement} element
  558. * @param {String} prop Wanted property
  559. * @returns {String|undefined} Element's style property
  560. */
  561. export function getStyle(element, prop) {
  562. /* eslint-disable */
  563. if (!element) {
  564. return;
  565. } else if (element === window) {
  566. if (prop === 'width') {
  567. return window.innerWidth + 'px';
  568. } else if (prop === 'height') {
  569. return window.innerHeight + 'px';
  570. }
  571. return;
  572. }
  573. var
  574. styleProp = element.style[prop],
  575. computedStyle;
  576. if (styleProp !== '' && styleProp !== void 0) {
  577. return styleProp;
  578. } else {
  579. computedStyle = getComputedStyle(element);
  580. if (computedStyle[prop] !== '' && computedStyle[prop] !== void 0) {
  581. return computedStyle[prop];
  582. }
  583. }
  584. }
  585. /**
  586. * Returns a computed style object for the provided element. (Needed if style is declared in external stylesheet).
  587. *
  588. * @param element
  589. * @returns {IEElementStyle|CssStyle} Elements computed style object
  590. */
  591. export function getComputedStyle(element) {
  592. return element.currentStyle || document.defaultView.getComputedStyle(element);
  593. }
  594. /**
  595. * Returns the element's outer width.
  596. *
  597. * @param element
  598. * @returns {number} Element's outer width
  599. */
  600. export function outerWidth(element) {
  601. return element.offsetWidth;
  602. }
  603. /**
  604. * Returns the element's outer height
  605. *
  606. * @param elem
  607. * @returns {number} Element's outer height
  608. */
  609. export function outerHeight(elem) {
  610. if (hasCaptionProblem() && elem.firstChild && elem.firstChild.nodeName === 'CAPTION') {
  611. // fixes problem with Firefox ignoring <caption> in TABLE.offsetHeight
  612. // jQuery (1.10.1) still has this unsolved
  613. // may be better to just switch to getBoundingClientRect
  614. // http://bililite.com/blog/2009/03/27/finding-the-size-of-a-table/
  615. // http://lists.w3.org/Archives/Public/www-style/2009Oct/0089.html
  616. // http://bugs.jquery.com/ticket/2196
  617. // http://lists.w3.org/Archives/Public/www-style/2009Oct/0140.html#start140
  618. return elem.offsetHeight + elem.firstChild.offsetHeight;
  619. }
  620. return elem.offsetHeight;
  621. }
  622. /**
  623. * Returns the element's inner height.
  624. *
  625. * @param element
  626. * @returns {number} Element's inner height
  627. */
  628. export function innerHeight(element) {
  629. return element.clientHeight || element.innerHeight;
  630. }
  631. /**
  632. * Returns the element's inner width.
  633. *
  634. * @param element
  635. * @returns {number} Element's inner width
  636. */
  637. export function innerWidth(element) {
  638. return element.clientWidth || element.innerWidth;
  639. }
  640. export function addEvent(element, event, callback) {
  641. if (window.addEventListener) {
  642. element.addEventListener(event, callback, false);
  643. } else {
  644. element.attachEvent('on' + event, callback);
  645. }
  646. }
  647. export function removeEvent(element, event, callback) {
  648. if (window.removeEventListener) {
  649. element.removeEventListener(event, callback, false);
  650. } else {
  651. element.detachEvent('on' + event, callback);
  652. }
  653. }
  654. /**
  655. * Returns caret position in text input
  656. *
  657. * @author http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
  658. * @return {Number}
  659. */
  660. export function getCaretPosition(el) {
  661. if (el.selectionStart) {
  662. return el.selectionStart;
  663. } else if (document.selection) { // IE8
  664. el.focus();
  665. let r = document.selection.createRange();
  666. if (r == null) {
  667. return 0;
  668. }
  669. let re = el.createTextRange();
  670. let rc = re.duplicate();
  671. re.moveToBookmark(r.getBookmark());
  672. rc.setEndPoint('EndToStart', re);
  673. return rc.text.length;
  674. }
  675. return 0;
  676. }
  677. /**
  678. * Returns end of the selection in text input
  679. *
  680. * @return {Number}
  681. */
  682. export function getSelectionEndPosition(el) {
  683. if (el.selectionEnd) {
  684. return el.selectionEnd;
  685. } else if (document.selection) { // IE8
  686. let r = document.selection.createRange();
  687. if (r == null) {
  688. return 0;
  689. }
  690. let re = el.createTextRange();
  691. return re.text.indexOf(r.text) + r.text.length;
  692. }
  693. return 0;
  694. }
  695. /**
  696. * Returns text under selection.
  697. *
  698. * @returns {String}
  699. */
  700. export function getSelectionText() {
  701. let text = '';
  702. if (window.getSelection) {
  703. text = window.getSelection().toString();
  704. } else if (document.selection && document.selection.type !== 'Control') {
  705. text = document.selection.createRange().text;
  706. }
  707. return text;
  708. }
  709. /**
  710. * Sets caret position in text input.
  711. *
  712. * @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
  713. * @param {Element} element
  714. * @param {Number} pos
  715. * @param {Number} endPos
  716. */
  717. export function setCaretPosition(element, pos, endPos) {
  718. if (endPos === void 0) {
  719. endPos = pos;
  720. }
  721. if (element.setSelectionRange) {
  722. element.focus();
  723. try {
  724. element.setSelectionRange(pos, endPos);
  725. } catch (err) {
  726. var elementParent = element.parentNode;
  727. var parentDisplayValue = elementParent.style.display;
  728. elementParent.style.display = 'block';
  729. element.setSelectionRange(pos, endPos);
  730. elementParent.style.display = parentDisplayValue;
  731. }
  732. } else if (element.createTextRange) { // IE8
  733. var range = element.createTextRange();
  734. range.collapse(true);
  735. range.moveEnd('character', endPos);
  736. range.moveStart('character', pos);
  737. range.select();
  738. }
  739. }
  740. var cachedScrollbarWidth;
  741. // http://stackoverflow.com/questions/986937/how-can-i-get-the-browsers-scrollbar-sizes
  742. function walkontableCalculateScrollbarWidth() {
  743. var inner = document.createElement('div');
  744. inner.style.height = '200px';
  745. inner.style.width = '100%';
  746. var outer = document.createElement('div');
  747. outer.style.boxSizing = 'content-box';
  748. outer.style.height = '150px';
  749. outer.style.left = '0px';
  750. outer.style.overflow = 'hidden';
  751. outer.style.position = 'absolute';
  752. outer.style.top = '0px';
  753. outer.style.width = '200px';
  754. outer.style.visibility = 'hidden';
  755. outer.appendChild(inner);
  756. (document.body || document.documentElement).appendChild(outer);
  757. var w1 = inner.offsetWidth;
  758. outer.style.overflow = 'scroll';
  759. var w2 = inner.offsetWidth;
  760. if (w1 == w2) {
  761. w2 = outer.clientWidth;
  762. }
  763. (document.body || document.documentElement).removeChild(outer);
  764. return (w1 - w2);
  765. }
  766. /**
  767. * Returns the computed width of the native browser scroll bar.
  768. *
  769. * @return {Number} width
  770. */
  771. export function getScrollbarWidth() {
  772. if (cachedScrollbarWidth === void 0) {
  773. cachedScrollbarWidth = walkontableCalculateScrollbarWidth();
  774. }
  775. return cachedScrollbarWidth;
  776. }
  777. /**
  778. * Checks if the provided element has a vertical scrollbar.
  779. *
  780. * @param {HTMLElement} element
  781. * @returns {Boolean}
  782. */
  783. export function hasVerticalScrollbar(element) {
  784. return element.offsetWidth !== element.clientWidth;
  785. }
  786. /**
  787. * Checks if the provided element has a vertical scrollbar.
  788. *
  789. * @param {HTMLElement} element
  790. * @returns {Boolean}
  791. */
  792. export function hasHorizontalScrollbar(element) {
  793. return element.offsetHeight !== element.clientHeight;
  794. }
  795. /**
  796. * Sets overlay position depending on it's type and used browser
  797. */
  798. export function setOverlayPosition(overlayElem, left, top) {
  799. if (isIE8() || isIE9()) {
  800. overlayElem.style.top = top;
  801. overlayElem.style.left = left;
  802. } else if (isSafari()) {
  803. overlayElem.style['-webkit-transform'] = 'translate3d(' + left + ',' + top + ',0)';
  804. } else {
  805. overlayElem.style.transform = 'translate3d(' + left + ',' + top + ',0)';
  806. }
  807. }
  808. export function getCssTransform(element) {
  809. var transform;
  810. if (element.style.transform && (transform = element.style.transform) !== '') {
  811. return ['transform', transform];
  812. } else if (element.style['-webkit-transform'] && (transform = element.style['-webkit-transform']) !== '') {
  813. return ['-webkit-transform', transform];
  814. }
  815. return -1;
  816. }
  817. export function resetCssTransform(element) {
  818. if (element.style.transform && element.style.transform !== '') {
  819. element.style.transform = '';
  820. } else if (element.style['-webkit-transform'] && element.style['-webkit-transform'] !== '') {
  821. element.style['-webkit-transform'] = '';
  822. }
  823. }
  824. /**
  825. * Determines if the given DOM element is an input field.
  826. * Notice: By 'input' we mean input, textarea and select nodes
  827. *
  828. * @param {HTMLElement} element - DOM element
  829. * @returns {Boolean}
  830. */
  831. export function isInput(element) {
  832. var inputs = ['INPUT', 'SELECT', 'TEXTAREA'];
  833. return element && (inputs.indexOf(element.nodeName) > -1 || element.contentEditable === 'true');
  834. }
  835. /**
  836. * Determines if the given DOM element is an input field placed OUTSIDE of HOT.
  837. * Notice: By 'input' we mean input, textarea and select nodes
  838. *
  839. * @param {HTMLElement} element - DOM element
  840. * @returns {Boolean}
  841. */
  842. export function isOutsideInput(element) {
  843. return isInput(element) && element.className.indexOf('handsontableInput') == -1 && element.className.indexOf('copyPaste') == -1;
  844. }