directives.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. import { onAttributeRemoved, onElRemoved } from './mutation'
  2. import { evaluate, evaluateLater } from './evaluator'
  3. import { elementBoundEffect } from './reactivity'
  4. import Alpine from './alpine'
  5. let prefixAsString = 'x-'
  6. export function prefix(subject = '') {
  7. return prefixAsString + subject
  8. }
  9. export function setPrefix(newPrefix) {
  10. prefixAsString = newPrefix
  11. }
  12. let directiveHandlers = {}
  13. export function directive(name, callback) {
  14. directiveHandlers[name] = callback
  15. }
  16. export function directives(el, attributes, originalAttributeOverride) {
  17. let transformedAttributeMap = {}
  18. let directives = Array.from(attributes)
  19. .map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName))
  20. .filter(outNonAlpineAttributes)
  21. .map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride))
  22. .sort(byPriority)
  23. return directives.map(directive => {
  24. return getDirectiveHandler(el, directive)
  25. })
  26. }
  27. let isDeferringHandlers = false
  28. let directiveHandlerStacks = new Map
  29. let currentHandlerStackKey = Symbol()
  30. export function deferHandlingDirectives(callback) {
  31. isDeferringHandlers = true
  32. let key = Symbol()
  33. currentHandlerStackKey = key
  34. directiveHandlerStacks.set(key, [])
  35. let flushHandlers = () => {
  36. while (directiveHandlerStacks.get(key).length) directiveHandlerStacks.get(key).shift()()
  37. directiveHandlerStacks.delete(key)
  38. }
  39. let stopDeferring = () => { isDeferringHandlers = false; flushHandlers() }
  40. callback(flushHandlers)
  41. stopDeferring()
  42. }
  43. export function getDirectiveHandler(el, directive) {
  44. let noop = () => {}
  45. let handler = directiveHandlers[directive.type] || noop
  46. let cleanups = []
  47. let cleanup = callback => cleanups.push(callback)
  48. let [effect, cleanupEffect] = elementBoundEffect(el)
  49. cleanups.push(cleanupEffect)
  50. let utilities = {
  51. Alpine,
  52. effect,
  53. cleanup,
  54. evaluateLater: evaluateLater.bind(evaluateLater, el),
  55. evaluate: evaluate.bind(evaluate, el),
  56. }
  57. let doCleanup = () => cleanups.forEach(i => i())
  58. onAttributeRemoved(el, directive.original, doCleanup)
  59. let fullHandler = () => {
  60. if (el._x_ignore || el._x_ignoreSelf) return
  61. handler.inline && handler.inline(el, directive, utilities)
  62. handler = handler.bind(handler, el, directive, utilities)
  63. isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler) : handler()
  64. }
  65. fullHandler.runCleanups = doCleanup
  66. return fullHandler
  67. }
  68. export let startingWith = (subject, replacement) => ({ name, value }) => {
  69. if (name.startsWith(subject)) name = name.replace(subject, replacement)
  70. return { name, value }
  71. }
  72. export let into = i => i
  73. function toTransformedAttributes(callback) {
  74. return ({ name, value }) => {
  75. let { name: newName, value: newValue } = attributeTransformers.reduce((carry, transform) => {
  76. return transform(carry)
  77. }, { name, value })
  78. if (newName !== name) callback(newName, name)
  79. return { name: newName, value: newValue }
  80. }
  81. }
  82. let attributeTransformers = []
  83. export function mapAttributes(callback) {
  84. attributeTransformers.push(callback)
  85. }
  86. function outNonAlpineAttributes({ name }) {
  87. return alpineAttributeRegex().test(name)
  88. }
  89. let alpineAttributeRegex = () => (new RegExp(`^${prefixAsString}([^:^.]+)\\b`))
  90. function toParsedDirectives(transformedAttributeMap, originalAttributeOverride) {
  91. return ({ name, value }) => {
  92. let typeMatch = name.match(alpineAttributeRegex())
  93. let valueMatch = name.match(/:([a-zA-Z0-9\-:]+)/)
  94. let modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
  95. let original = originalAttributeOverride || transformedAttributeMap[name] || name
  96. return {
  97. type: typeMatch ? typeMatch[1] : null,
  98. value: valueMatch ? valueMatch[1] : null,
  99. modifiers: modifiers.map(i => i.replace('.', '')),
  100. expression: value,
  101. original,
  102. }
  103. }
  104. }
  105. const DEFAULT = 'DEFAULT'
  106. let directiveOrder = [
  107. 'ignore',
  108. 'ref',
  109. 'data',
  110. 'bind',
  111. 'init',
  112. 'for',
  113. 'model',
  114. 'transition',
  115. 'show',
  116. 'if',
  117. DEFAULT,
  118. 'element',
  119. ]
  120. function byPriority(a, b) {
  121. let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type
  122. let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type
  123. return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB)
  124. }