Нема описа

directives.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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. return {
  16. before(directive) {
  17. if (!directiveHandlers[directive]) {
  18. console.warn(
  19. "Cannot find directive `${directive}`. "
  20. + "`${name}` will use the default order of execution"
  21. );
  22. return;
  23. }
  24. const pos = directiveOrder.indexOf(directive);
  25. directiveOrder.splice(pos >= 0 ? pos : directiveOrder.indexOf('DEFAULT'), 0, name);
  26. }
  27. }
  28. }
  29. export function directives(el, attributes, originalAttributeOverride) {
  30. attributes = Array.from(attributes)
  31. if (el._x_virtualDirectives) {
  32. let vAttributes = Object.entries(el._x_virtualDirectives).map(([name, value]) => ({ name, value }))
  33. let staticAttributes = attributesOnly(vAttributes)
  34. // Handle binding normal HTML attributes (non-Alpine directives).
  35. vAttributes = vAttributes.map(attribute => {
  36. if (staticAttributes.find(attr => attr.name === attribute.name)) {
  37. return {
  38. name: `x-bind:${attribute.name}`,
  39. value: `"${attribute.value}"`,
  40. }
  41. }
  42. return attribute
  43. })
  44. attributes = attributes.concat(vAttributes)
  45. }
  46. let transformedAttributeMap = {}
  47. let directives = attributes
  48. .map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName))
  49. .filter(outNonAlpineAttributes)
  50. .map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride))
  51. .sort(byPriority)
  52. return directives.map(directive => {
  53. return getDirectiveHandler(el, directive)
  54. })
  55. }
  56. export function attributesOnly(attributes) {
  57. return Array.from(attributes)
  58. .map(toTransformedAttributes())
  59. .filter(attr => ! outNonAlpineAttributes(attr))
  60. }
  61. let isDeferringHandlers = false
  62. let directiveHandlerStacks = new Map
  63. let currentHandlerStackKey = Symbol()
  64. export function deferHandlingDirectives(callback) {
  65. isDeferringHandlers = true
  66. let key = Symbol()
  67. currentHandlerStackKey = key
  68. directiveHandlerStacks.set(key, [])
  69. let flushHandlers = () => {
  70. while (directiveHandlerStacks.get(key).length) directiveHandlerStacks.get(key).shift()()
  71. directiveHandlerStacks.delete(key)
  72. }
  73. let stopDeferring = () => { isDeferringHandlers = false; flushHandlers() }
  74. callback(flushHandlers)
  75. stopDeferring()
  76. }
  77. export function getElementBoundUtilities(el) {
  78. let cleanups = []
  79. let cleanup = callback => cleanups.push(callback)
  80. let [effect, cleanupEffect] = elementBoundEffect(el)
  81. cleanups.push(cleanupEffect)
  82. let utilities = {
  83. Alpine,
  84. effect,
  85. cleanup,
  86. evaluateLater: evaluateLater.bind(evaluateLater, el),
  87. evaluate: evaluate.bind(evaluate, el),
  88. }
  89. let doCleanup = () => cleanups.forEach(i => i())
  90. return [utilities, doCleanup]
  91. }
  92. export function getDirectiveHandler(el, directive) {
  93. let noop = () => {}
  94. let handler = directiveHandlers[directive.type] || noop
  95. let [utilities, cleanup] = getElementBoundUtilities(el)
  96. onAttributeRemoved(el, directive.original, cleanup)
  97. let fullHandler = () => {
  98. if (el._x_ignore || el._x_ignoreSelf) return
  99. handler.inline && handler.inline(el, directive, utilities)
  100. handler = handler.bind(handler, el, directive, utilities)
  101. isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler) : handler()
  102. }
  103. fullHandler.runCleanups = cleanup
  104. return fullHandler
  105. }
  106. export let startingWith = (subject, replacement) => ({ name, value }) => {
  107. if (name.startsWith(subject)) name = name.replace(subject, replacement)
  108. return { name, value }
  109. }
  110. export let into = i => i
  111. function toTransformedAttributes(callback = () => {}) {
  112. return ({ name, value }) => {
  113. let { name: newName, value: newValue } = attributeTransformers.reduce((carry, transform) => {
  114. return transform(carry)
  115. }, { name, value })
  116. if (newName !== name) callback(newName, name)
  117. return { name: newName, value: newValue }
  118. }
  119. }
  120. let attributeTransformers = []
  121. export function mapAttributes(callback) {
  122. attributeTransformers.push(callback)
  123. }
  124. function outNonAlpineAttributes({ name }) {
  125. return alpineAttributeRegex().test(name)
  126. }
  127. let alpineAttributeRegex = () => (new RegExp(`^${prefixAsString}([^:^.]+)\\b`))
  128. function toParsedDirectives(transformedAttributeMap, originalAttributeOverride) {
  129. return ({ name, value }) => {
  130. let typeMatch = name.match(alpineAttributeRegex())
  131. let valueMatch = name.match(/:([a-zA-Z0-9\-:]+)/)
  132. let modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
  133. let original = originalAttributeOverride || transformedAttributeMap[name] || name
  134. return {
  135. type: typeMatch ? typeMatch[1] : null,
  136. value: valueMatch ? valueMatch[1] : null,
  137. modifiers: modifiers.map(i => i.replace('.', '')),
  138. expression: value,
  139. original,
  140. }
  141. }
  142. }
  143. const DEFAULT = 'DEFAULT'
  144. let directiveOrder = [
  145. 'ignore',
  146. 'ref',
  147. 'data',
  148. 'id',
  149. 'bind',
  150. 'init',
  151. 'for',
  152. 'model',
  153. 'modelable',
  154. 'transition',
  155. 'show',
  156. 'if',
  157. DEFAULT,
  158. 'teleport',
  159. ]
  160. function byPriority(a, b) {
  161. let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type
  162. let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type
  163. return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB)
  164. }