utils.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // Thanks @stimulus:
  2. // https://github.com/stimulusjs/stimulus/blob/master/packages/%40stimulus/core/src/application.ts
  3. export function domReady() {
  4. return new Promise(resolve => {
  5. if (document.readyState == "loading") {
  6. document.addEventListener("DOMContentLoaded", resolve)
  7. } else {
  8. resolve()
  9. }
  10. })
  11. }
  12. export function arrayUnique(array) {
  13. var a = array.concat();
  14. for(var i=0; i<a.length; ++i) {
  15. for(var j=i+1; j<a.length; ++j) {
  16. if(a[i] === a[j])
  17. a.splice(j--, 1);
  18. }
  19. }
  20. return a;
  21. }
  22. export function isTesting() {
  23. return navigator.userAgent, navigator.userAgent.includes("Node.js")
  24. || navigator.userAgent.includes("jsdom")
  25. }
  26. export function kebabCase(subject) {
  27. return subject.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[_\s]/, '-').toLowerCase()
  28. }
  29. export function keyToModifier(key) {
  30. switch (key) {
  31. case ' ':
  32. case 'Spacebar':
  33. return 'space'
  34. default:
  35. return kebabCase(key)
  36. }
  37. }
  38. export function walkSkippingNestedComponents(el, callback, isRoot = true) {
  39. if (el.hasAttribute('x-data') && ! isRoot) return
  40. callback(el)
  41. let node = el.firstElementChild
  42. while (node) {
  43. walkSkippingNestedComponents(node, callback, false)
  44. node = node.nextElementSibling
  45. }
  46. }
  47. export function debounce(func, wait) {
  48. var timeout
  49. return function () {
  50. var context = this, args = arguments
  51. var later = function () {
  52. timeout = null
  53. func.apply(context, args)
  54. }
  55. clearTimeout(timeout)
  56. timeout = setTimeout(later, wait)
  57. }
  58. }
  59. export function saferEval(expression, dataContext, additionalHelperVariables = {}) {
  60. return (new Function(['$data', ...Object.keys(additionalHelperVariables)], `var result; with($data) { result = ${expression} }; return result`))(
  61. dataContext, ...Object.values(additionalHelperVariables)
  62. )
  63. }
  64. export function saferEvalNoReturn(expression, dataContext, additionalHelperVariables = {}) {
  65. return (new Function(['dataContext', ...Object.keys(additionalHelperVariables)], `with(dataContext) { ${expression} }`))(
  66. dataContext, ...Object.values(additionalHelperVariables)
  67. )
  68. }
  69. export function isXAttr(attr) {
  70. const name = replaceAtAndColonWithStandardSyntax(attr.name)
  71. const xAttrRE = /x-(on|bind|data|text|html|model|if|show|cloak|transition|ref)/
  72. return xAttrRE.test(name)
  73. }
  74. export function getXAttrs(el, type) {
  75. return Array.from(el.attributes)
  76. .filter(isXAttr)
  77. .map(attr => {
  78. const name = replaceAtAndColonWithStandardSyntax(attr.name)
  79. const typeMatch = name.match(/x-(on|bind|data|text|html|model|if|show|cloak|transition|ref)/)
  80. const valueMatch = name.match(/:([a-zA-Z\-:]+)/)
  81. const modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
  82. return {
  83. type: typeMatch ? typeMatch[1] : null,
  84. value: valueMatch ? valueMatch[1] : null,
  85. modifiers: modifiers.map(i => i.replace('.', '')),
  86. expression: attr.value,
  87. }
  88. })
  89. .filter(i => {
  90. // If no type is passed in for filtering, bypassfilter
  91. if (! type) return true
  92. return i.type === type
  93. })
  94. }
  95. export function replaceAtAndColonWithStandardSyntax(name) {
  96. if (name.startsWith('@')) {
  97. return name.replace('@', 'x-on:')
  98. } else if (name.startsWith(':')) {
  99. return name.replace(':', 'x-bind:')
  100. }
  101. return name
  102. }
  103. export function transitionIn(el, callback, forceSkip = false) {
  104. if (forceSkip) return callback()
  105. const attrs = getXAttrs(el, 'transition')
  106. if (attrs.length < 1) return callback()
  107. const enter = (attrs.find(i => i.value === 'enter') || { expression: '' }).expression.split(' ').filter(i => i !== '')
  108. const enterStart = (attrs.find(i => i.value === 'enter-start') || { expression: '' }).expression.split(' ').filter(i => i !== '')
  109. const enterEnd = (attrs.find(i => i.value === 'enter-end') || { expression: '' }).expression.split(' ').filter(i => i !== '')
  110. transition(el, enter, enterStart, enterEnd, callback, () => {})
  111. }
  112. export function transitionOut(el, callback, forceSkip = false) {
  113. if (forceSkip) return callback()
  114. const attrs = getXAttrs(el, 'transition')
  115. if (attrs.length < 1) return callback()
  116. const leave = (attrs.find(i => i.value === 'leave') || { expression: '' }).expression.split(' ').filter(i => i !== '')
  117. const leaveStart = (attrs.find(i => i.value === 'leave-start') || { expression: '' }).expression.split(' ').filter(i => i !== '')
  118. const leaveEnd = (attrs.find(i => i.value === 'leave-end') || { expression: '' }).expression.split(' ').filter(i => i !== '')
  119. transition(el, leave, leaveStart, leaveEnd, () => {}, callback)
  120. }
  121. export function transition(el, classesDuring, classesStart, classesEnd, hook1, hook2) {
  122. el.classList.add(...classesStart)
  123. el.classList.add(...classesDuring)
  124. requestAnimationFrame(() => {
  125. const duration = Number(getComputedStyle(el).transitionDuration.replace('s', '')) * 1000
  126. hook1()
  127. requestAnimationFrame(() => {
  128. el.classList.remove(...classesStart)
  129. el.classList.add(...classesEnd)
  130. setTimeout(() => {
  131. hook2()
  132. // Adding an "isConnected" check, in case the callback
  133. // removed the element from the DOM.
  134. if (el.isConnected) {
  135. el.classList.remove(...classesDuring)
  136. el.classList.remove(...classesEnd)
  137. }
  138. }, duration);
  139. })
  140. });
  141. }