diff options
Diffstat (limited to 'alpinejs/packages/alpinejs/src/directives.js')
-rw-r--r-- | alpinejs/packages/alpinejs/src/directives.js | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/alpinejs/packages/alpinejs/src/directives.js b/alpinejs/packages/alpinejs/src/directives.js new file mode 100644 index 0000000..906822f --- /dev/null +++ b/alpinejs/packages/alpinejs/src/directives.js @@ -0,0 +1,179 @@ +import { onAttributeRemoved, onElRemoved } from './mutation' +import { evaluate, evaluateLater } from './evaluator' +import { elementBoundEffect } from './reactivity' +import Alpine from './alpine' + +let prefixAsString = 'x-' + +export function prefix(subject = '') { + return prefixAsString + subject +} + +export function setPrefix(newPrefix) { + prefixAsString = newPrefix +} + +let directiveHandlers = {} + +export function directive(name, callback) { + directiveHandlers[name] = callback +} + +export function directives(el, attributes, originalAttributeOverride) { + let transformedAttributeMap = {} + + let directives = Array.from(attributes) + .map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName)) + .filter(outNonAlpineAttributes) + .map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride)) + .sort(byPriority) + + return directives.map(directive => { + return getDirectiveHandler(el, directive) + }) +} + +export function attributesOnly(attributes) { + return Array.from(attributes) + .map(toTransformedAttributes()) + .filter(attr => ! outNonAlpineAttributes(attr)) +} + +let isDeferringHandlers = false +let directiveHandlerStacks = new Map +let currentHandlerStackKey = Symbol() + +export function deferHandlingDirectives(callback) { + isDeferringHandlers = true + + let key = Symbol() + + currentHandlerStackKey = key + + directiveHandlerStacks.set(key, []) + + let flushHandlers = () => { + while (directiveHandlerStacks.get(key).length) directiveHandlerStacks.get(key).shift()() + + directiveHandlerStacks.delete(key) + } + + let stopDeferring = () => { isDeferringHandlers = false; flushHandlers() } + + callback(flushHandlers) + + stopDeferring() +} + +export function getDirectiveHandler(el, directive) { + let noop = () => {} + + let handler = directiveHandlers[directive.type] || noop + + let cleanups = [] + + let cleanup = callback => cleanups.push(callback) + + let [effect, cleanupEffect] = elementBoundEffect(el) + + cleanups.push(cleanupEffect) + + let utilities = { + Alpine, + effect, + cleanup, + evaluateLater: evaluateLater.bind(evaluateLater, el), + evaluate: evaluate.bind(evaluate, el), + } + + let doCleanup = () => cleanups.forEach(i => i()) + + onAttributeRemoved(el, directive.original, doCleanup) + + let fullHandler = () => { + if (el._x_ignore || el._x_ignoreSelf) return + + handler.inline && handler.inline(el, directive, utilities) + + handler = handler.bind(handler, el, directive, utilities) + + isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler) : handler() + } + + fullHandler.runCleanups = doCleanup + + return fullHandler +} + +export let startingWith = (subject, replacement) => ({ name, value }) => { + if (name.startsWith(subject)) name = name.replace(subject, replacement) + + return { name, value } +} + +export let into = i => i + +function toTransformedAttributes(callback = () => {}) { + return ({ name, value }) => { + let { name: newName, value: newValue } = attributeTransformers.reduce((carry, transform) => { + return transform(carry) + }, { name, value }) + + if (newName !== name) callback(newName, name) + + return { name: newName, value: newValue } + } +} + +let attributeTransformers = [] + +export function mapAttributes(callback) { + attributeTransformers.push(callback) +} + +function outNonAlpineAttributes({ name }) { + return alpineAttributeRegex().test(name) +} + +let alpineAttributeRegex = () => (new RegExp(`^${prefixAsString}([^:^.]+)\\b`)) + +function toParsedDirectives(transformedAttributeMap, originalAttributeOverride) { + return ({ name, value }) => { + let typeMatch = name.match(alpineAttributeRegex()) + let valueMatch = name.match(/:([a-zA-Z0-9\-:]+)/) + let modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || [] + let original = originalAttributeOverride || transformedAttributeMap[name] || name + + return { + type: typeMatch ? typeMatch[1] : null, + value: valueMatch ? valueMatch[1] : null, + modifiers: modifiers.map(i => i.replace('.', '')), + expression: value, + original, + } + } +} + +const DEFAULT = 'DEFAULT' + +let directiveOrder = [ + 'ignore', + 'ref', + 'data', + 'bind', + 'init', + 'for', + 'model', + 'transition', + 'show', + 'if', + DEFAULT, + 'element', +] + +function byPriority(a, b) { + let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type + let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type + + return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB) +} |