酒店预订平台
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.

utils.js 13 KiB

3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import { IE11OrLess } from './BrowserInfo.js';
  2. import Sortable from './Sortable.js';
  3. const captureMode = {
  4. capture: false,
  5. passive: false
  6. };
  7. function on(el, event, fn) {
  8. el.addEventListener(event, fn, !IE11OrLess && captureMode);
  9. }
  10. function off(el, event, fn) {
  11. el.removeEventListener(event, fn, !IE11OrLess && captureMode);
  12. }
  13. function matches(/**HTMLElement*/el, /**String*/selector) {
  14. if (!selector) return;
  15. selector[0] === '>' && (selector = selector.substring(1));
  16. if (el) {
  17. try {
  18. if (el.matches) {
  19. return el.matches(selector);
  20. } else if (el.msMatchesSelector) {
  21. return el.msMatchesSelector(selector);
  22. } else if (el.webkitMatchesSelector) {
  23. return el.webkitMatchesSelector(selector);
  24. }
  25. } catch(_) {
  26. return false;
  27. }
  28. }
  29. return false;
  30. }
  31. function getParentOrHost(el) {
  32. return (el.host && el !== document && el.host.nodeType)
  33. ? el.host
  34. : el.parentNode;
  35. }
  36. function closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx, includeCTX) {
  37. if (el) {
  38. ctx = ctx || document;
  39. do {
  40. if (
  41. selector != null &&
  42. (
  43. selector[0] === '>' ?
  44. el.parentNode === ctx && matches(el, selector) :
  45. matches(el, selector)
  46. ) ||
  47. includeCTX && el === ctx
  48. ) {
  49. return el;
  50. }
  51. if (el === ctx) break;
  52. /* jshint boss:true */
  53. } while (el = getParentOrHost(el));
  54. }
  55. return null;
  56. }
  57. const R_SPACE = /\s+/g;
  58. function toggleClass(el, name, state) {
  59. if (el && name) {
  60. if (el.classList) {
  61. el.classList[state ? 'add' : 'remove'](name);
  62. }
  63. else {
  64. let className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' ');
  65. el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' ');
  66. }
  67. }
  68. }
  69. function css(el, prop, val) {
  70. let style = el && el.style;
  71. if (style) {
  72. if (val === void 0) {
  73. if (document.defaultView && document.defaultView.getComputedStyle) {
  74. val = document.defaultView.getComputedStyle(el, '');
  75. }
  76. else if (el.currentStyle) {
  77. val = el.currentStyle;
  78. }
  79. return prop === void 0 ? val : val[prop];
  80. }
  81. else {
  82. if (!(prop in style) && prop.indexOf('webkit') === -1) {
  83. prop = '-webkit-' + prop;
  84. }
  85. style[prop] = val + (typeof val === 'string' ? '' : 'px');
  86. }
  87. }
  88. }
  89. function matrix(el, selfOnly) {
  90. let appliedTransforms = '';
  91. if (typeof(el) === 'string') {
  92. appliedTransforms = el;
  93. } else {
  94. do {
  95. let transform = css(el, 'transform');
  96. if (transform && transform !== 'none') {
  97. appliedTransforms = transform + ' ' + appliedTransforms;
  98. }
  99. /* jshint boss:true */
  100. } while (!selfOnly && (el = el.parentNode));
  101. }
  102. const matrixFn = window.DOMMatrix || window.WebKitCSSMatrix || window.CSSMatrix || window.MSCSSMatrix;
  103. /*jshint -W056 */
  104. return matrixFn && (new matrixFn(appliedTransforms));
  105. }
  106. function find(ctx, tagName, iterator) {
  107. if (ctx) {
  108. let list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
  109. if (iterator) {
  110. for (; i < n; i++) {
  111. iterator(list[i], i);
  112. }
  113. }
  114. return list;
  115. }
  116. return [];
  117. }
  118. function getWindowScrollingElement() {
  119. let scrollingElement = document.scrollingElement;
  120. if (scrollingElement) {
  121. return scrollingElement
  122. } else {
  123. return document.documentElement
  124. }
  125. }
  126. /**
  127. * Returns the "bounding client rect" of given element
  128. * @param {HTMLElement} el The element whose boundingClientRect is wanted
  129. * @param {[Boolean]} relativeToContainingBlock Whether the rect should be relative to the containing block of (including) the container
  130. * @param {[Boolean]} relativeToNonStaticParent Whether the rect should be relative to the relative parent of (including) the contaienr
  131. * @param {[Boolean]} undoScale Whether the container's scale() should be undone
  132. * @param {[HTMLElement]} container The parent the element will be placed in
  133. * @return {Object} The boundingClientRect of el, with specified adjustments
  134. */
  135. function getRect(el, relativeToContainingBlock, relativeToNonStaticParent, undoScale, container) {
  136. if (!el.getBoundingClientRect && el !== window) return;
  137. let elRect,
  138. top,
  139. left,
  140. bottom,
  141. right,
  142. height,
  143. width;
  144. if (el !== window && el !== getWindowScrollingElement()) {
  145. elRect = el.getBoundingClientRect();
  146. top = elRect.top;
  147. left = elRect.left;
  148. bottom = elRect.bottom;
  149. right = elRect.right;
  150. height = elRect.height;
  151. width = elRect.width;
  152. } else {
  153. top = 0;
  154. left = 0;
  155. bottom = window.innerHeight;
  156. right = window.innerWidth;
  157. height = window.innerHeight;
  158. width = window.innerWidth;
  159. }
  160. if ((relativeToContainingBlock || relativeToNonStaticParent) && el !== window) {
  161. // Adjust for translate()
  162. container = container || el.parentNode;
  163. // solves #1123 (see: https://stackoverflow.com/a/37953806/6088312)
  164. // Not needed on <= IE11
  165. if (!IE11OrLess) {
  166. do {
  167. if (
  168. container &&
  169. container.getBoundingClientRect &&
  170. (
  171. css(container, 'transform') !== 'none' ||
  172. relativeToNonStaticParent &&
  173. css(container, 'position') !== 'static'
  174. )
  175. ) {
  176. let containerRect = container.getBoundingClientRect();
  177. // Set relative to edges of padding box of container
  178. top -= containerRect.top + parseInt(css(container, 'border-top-width'));
  179. left -= containerRect.left + parseInt(css(container, 'border-left-width'));
  180. bottom = top + elRect.height;
  181. right = left + elRect.width;
  182. break;
  183. }
  184. /* jshint boss:true */
  185. } while (container = container.parentNode);
  186. }
  187. }
  188. if (undoScale && el !== window) {
  189. // Adjust for scale()
  190. let elMatrix = matrix(container || el),
  191. scaleX = elMatrix && elMatrix.a,
  192. scaleY = elMatrix && elMatrix.d;
  193. if (elMatrix) {
  194. top /= scaleY;
  195. left /= scaleX;
  196. width /= scaleX;
  197. height /= scaleY;
  198. bottom = top + height;
  199. right = left + width;
  200. }
  201. }
  202. return {
  203. top: top,
  204. left: left,
  205. bottom: bottom,
  206. right: right,
  207. width: width,
  208. height: height
  209. };
  210. }
  211. /**
  212. * Checks if a side of an element is scrolled past a side of its parents
  213. * @param {HTMLElement} el The element who's side being scrolled out of view is in question
  214. * @param {String} elSide Side of the element in question ('top', 'left', 'right', 'bottom')
  215. * @param {String} parentSide Side of the parent in question ('top', 'left', 'right', 'bottom')
  216. * @return {HTMLElement} The parent scroll element that the el's side is scrolled past, or null if there is no such element
  217. */
  218. function isScrolledPast(el, elSide, parentSide) {
  219. let parent = getParentAutoScrollElement(el, true),
  220. elSideVal = getRect(el)[elSide];
  221. /* jshint boss:true */
  222. while (parent) {
  223. let parentSideVal = getRect(parent)[parentSide],
  224. visible;
  225. if (parentSide === 'top' || parentSide === 'left') {
  226. visible = elSideVal >= parentSideVal;
  227. } else {
  228. visible = elSideVal <= parentSideVal;
  229. }
  230. if (!visible) return parent;
  231. if (parent === getWindowScrollingElement()) break;
  232. parent = getParentAutoScrollElement(parent, false);
  233. }
  234. return false;
  235. }
  236. /**
  237. * Gets nth child of el, ignoring hidden children, sortable's elements (does not ignore clone if it's visible)
  238. * and non-draggable elements
  239. * @param {HTMLElement} el The parent element
  240. * @param {Number} childNum The index of the child
  241. * @param {Object} options Parent Sortable's options
  242. * @return {HTMLElement} The child at index childNum, or null if not found
  243. */
  244. function getChild(el, childNum, options) {
  245. let currentChild = 0,
  246. i = 0,
  247. children = el.children;
  248. while (i < children.length) {
  249. if (
  250. children[i].style.display !== 'none' &&
  251. children[i] !== Sortable.ghost &&
  252. children[i] !== Sortable.dragged &&
  253. closest(children[i], options.draggable, el, false)
  254. ) {
  255. if (currentChild === childNum) {
  256. return children[i];
  257. }
  258. currentChild++;
  259. }
  260. i++;
  261. }
  262. return null;
  263. }
  264. /**
  265. * Gets the last child in the el, ignoring ghostEl or invisible elements (clones)
  266. * @param {HTMLElement} el Parent element
  267. * @param {selector} selector Any other elements that should be ignored
  268. * @return {HTMLElement} The last child, ignoring ghostEl
  269. */
  270. function lastChild(el, selector) {
  271. let last = el.lastElementChild;
  272. while (
  273. last &&
  274. (
  275. last === Sortable.ghost ||
  276. css(last, 'display') === 'none' ||
  277. selector && !matches(last, selector)
  278. )
  279. ) {
  280. last = last.previousElementSibling;
  281. }
  282. return last || null;
  283. }
  284. /**
  285. * Returns the index of an element within its parent for a selected set of
  286. * elements
  287. * @param {HTMLElement} el
  288. * @param {selector} selector
  289. * @return {number}
  290. */
  291. function index(el, selector) {
  292. let index = 0;
  293. if (!el || !el.parentNode) {
  294. return -1;
  295. }
  296. /* jshint boss:true */
  297. while (el = el.previousElementSibling) {
  298. if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && el !== Sortable.clone && (!selector || matches(el, selector))) {
  299. index++;
  300. }
  301. }
  302. return index;
  303. }
  304. /**
  305. * Returns the scroll offset of the given element, added with all the scroll offsets of parent elements.
  306. * The value is returned in real pixels.
  307. * @param {HTMLElement} el
  308. * @return {Array} Offsets in the format of [left, top]
  309. */
  310. function getRelativeScrollOffset(el) {
  311. let offsetLeft = 0,
  312. offsetTop = 0,
  313. winScroller = getWindowScrollingElement();
  314. if (el) {
  315. do {
  316. let elMatrix = matrix(el),
  317. scaleX = elMatrix.a,
  318. scaleY = elMatrix.d;
  319. offsetLeft += el.scrollLeft * scaleX;
  320. offsetTop += el.scrollTop * scaleY;
  321. } while (el !== winScroller && (el = el.parentNode));
  322. }
  323. return [offsetLeft, offsetTop];
  324. }
  325. /**
  326. * Returns the index of the object within the given array
  327. * @param {Array} arr Array that may or may not hold the object
  328. * @param {Object} obj An object that has a key-value pair unique to and identical to a key-value pair in the object you want to find
  329. * @return {Number} The index of the object in the array, or -1
  330. */
  331. function indexOfObject(arr, obj) {
  332. for (let i in arr) {
  333. if (!arr.hasOwnProperty(i)) continue;
  334. for (let key in obj) {
  335. if (obj.hasOwnProperty(key) && obj[key] === arr[i][key]) return Number(i);
  336. }
  337. }
  338. return -1;
  339. }
  340. function getParentAutoScrollElement(el, includeSelf) {
  341. // skip to window
  342. if (!el || !el.getBoundingClientRect) return getWindowScrollingElement();
  343. let elem = el;
  344. let gotSelf = false;
  345. do {
  346. // we don't need to get elem css if it isn't even overflowing in the first place (performance)
  347. if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) {
  348. let elemCSS = css(elem);
  349. if (
  350. elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll') ||
  351. elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll')
  352. ) {
  353. if (!elem.getBoundingClientRect || elem === document.body) return getWindowScrollingElement();
  354. if (gotSelf || includeSelf) return elem;
  355. gotSelf = true;
  356. }
  357. }
  358. /* jshint boss:true */
  359. } while (elem = elem.parentNode);
  360. return getWindowScrollingElement();
  361. }
  362. function extend(dst, src) {
  363. if (dst && src) {
  364. for (let key in src) {
  365. if (src.hasOwnProperty(key)) {
  366. dst[key] = src[key];
  367. }
  368. }
  369. }
  370. return dst;
  371. }
  372. function isRectEqual(rect1, rect2) {
  373. return Math.round(rect1.top) === Math.round(rect2.top) &&
  374. Math.round(rect1.left) === Math.round(rect2.left) &&
  375. Math.round(rect1.height) === Math.round(rect2.height) &&
  376. Math.round(rect1.width) === Math.round(rect2.width);
  377. }
  378. let _throttleTimeout;
  379. function throttle(callback, ms) {
  380. return function () {
  381. if (!_throttleTimeout) {
  382. let args = arguments,
  383. _this = this;
  384. if (args.length === 1) {
  385. callback.call(_this, args[0]);
  386. } else {
  387. callback.apply(_this, args);
  388. }
  389. _throttleTimeout = setTimeout(function () {
  390. _throttleTimeout = void 0;
  391. }, ms);
  392. }
  393. };
  394. }
  395. function cancelThrottle() {
  396. clearTimeout(_throttleTimeout);
  397. _throttleTimeout = void 0;
  398. }
  399. function scrollBy(el, x, y) {
  400. el.scrollLeft += x;
  401. el.scrollTop += y;
  402. }
  403. function clone(el) {
  404. let Polymer = window.Polymer;
  405. let $ = window.jQuery || window.Zepto;
  406. if (Polymer && Polymer.dom) {
  407. return Polymer.dom(el).cloneNode(true);
  408. }
  409. else if ($) {
  410. return $(el).clone(true)[0];
  411. }
  412. else {
  413. return el.cloneNode(true);
  414. }
  415. }
  416. function setRect(el, rect) {
  417. css(el, 'position', 'absolute');
  418. css(el, 'top', rect.top);
  419. css(el, 'left', rect.left);
  420. css(el, 'width', rect.width);
  421. css(el, 'height', rect.height);
  422. }
  423. function unsetRect(el) {
  424. css(el, 'position', '');
  425. css(el, 'top', '');
  426. css(el, 'left', '');
  427. css(el, 'width', '');
  428. css(el, 'height', '');
  429. }
  430. const expando = 'Sortable' + (new Date).getTime();
  431. export {
  432. on,
  433. off,
  434. matches,
  435. getParentOrHost,
  436. closest,
  437. toggleClass,
  438. css,
  439. matrix,
  440. find,
  441. getWindowScrollingElement,
  442. getRect,
  443. isScrolledPast,
  444. getChild,
  445. lastChild,
  446. index,
  447. getRelativeScrollOffset,
  448. indexOfObject,
  449. getParentAutoScrollElement,
  450. extend,
  451. isRectEqual,
  452. throttle,
  453. cancelThrottle,
  454. scrollBy,
  455. clone,
  456. setRect,
  457. unsetRect,
  458. expando
  459. };