(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Alpine = factory()); }(this, (function () { 'use strict'; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } // Thanks @stimulus: // https://github.com/stimulusjs/stimulus/blob/master/packages/%40stimulus/core/src/application.ts function domReady() { return new Promise(resolve => { if (document.readyState == "loading") { document.addEventListener("DOMContentLoaded", resolve); } else { resolve(); } }); } function arrayUnique(array) { var a = array.concat(); for (var i = 0; i < a.length; ++i) { for (var j = i + 1; j < a.length; ++j) { if (a[i] === a[j]) a.splice(j--, 1); } } return a; } function isTesting() { return navigator.userAgent.includes("Node.js") || navigator.userAgent.includes("jsdom"); } function kebabCase(subject) { return subject.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[_\s]/, '-').toLowerCase(); } function walk(el, callback) { if (callback(el) === false) return; let node = el.firstElementChild; while (node) { walk(node, callback); node = node.nextElementSibling; } } function debounce(func, wait) { var timeout; return function () { var context = this, args = arguments; var later = function later() { timeout = null; func.apply(context, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function saferEval(expression, dataContext, additionalHelperVariables = {}) { return new Function(['$data', ...Object.keys(additionalHelperVariables)], `var result; with($data) { result = ${expression} }; return result`)(dataContext, ...Object.values(additionalHelperVariables)); } function saferEvalNoReturn(expression, dataContext, additionalHelperVariables = {}) { // For the cases when users pass only a function reference to the caller: `x-on:click="foo"` // Where "foo" is a function. Also, we'll pass the function the event instance when we call it. if (Object.keys(dataContext).includes(expression)) { let methodReference = new Function(['dataContext', ...Object.keys(additionalHelperVariables)], `with(dataContext) { return ${expression} }`)(dataContext, ...Object.values(additionalHelperVariables)); if (typeof methodReference === 'function') { return methodReference.call(dataContext, additionalHelperVariables['$event']); } } return new Function(['dataContext', ...Object.keys(additionalHelperVariables)], `with(dataContext) { ${expression} }`)(dataContext, ...Object.values(additionalHelperVariables)); } const xAttrRE = /^x-(on|bind|data|text|html|model|if|for|show|cloak|transition|ref)\b/; function isXAttr(attr) { const name = replaceAtAndColonWithStandardSyntax(attr.name); return xAttrRE.test(name); } function getXAttrs(el, type) { return Array.from(el.attributes).filter(isXAttr).map(attr => { const name = replaceAtAndColonWithStandardSyntax(attr.name); const typeMatch = name.match(xAttrRE); const valueMatch = name.match(/:([a-zA-Z\-:]+)/); const modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []; return { type: typeMatch ? typeMatch[1] : null, value: valueMatch ? valueMatch[1] : null, modifiers: modifiers.map(i => i.replace('.', '')), expression: attr.value }; }).filter(i => { // If no type is passed in for filtering, bypass filter if (!type) return true; return i.type === type; }); } function isBooleanAttr(attrName) { // As per HTML spec table https://html.spec.whatwg.org/multipage/indices.html#attributes-3:boolean-attribute // Array roughly ordered by estimated usage const booleanAttributes = ['disabled', 'checked', 'required', 'readonly', 'hidden', 'open', 'selected', 'autofocus', 'itemscope', 'multiple', 'novalidate', 'allowfullscreen', 'allowpaymentrequest', 'formnovalidate', 'autoplay', 'controls', 'loop', 'muted', 'playsinline', 'default', 'ismap', 'reversed', 'async', 'defer', 'nomodule']; return booleanAttributes.includes(attrName); } function replaceAtAndColonWithStandardSyntax(name) { if (name.startsWith('@')) { return name.replace('@', 'x-on:'); } else if (name.startsWith(':')) { return name.replace(':', 'x-bind:'); } return name; } function transitionIn(el, show, forceSkip = false) { // We don't want to transition on the initial page load. if (forceSkip) return show(); const attrs = getXAttrs(el, 'transition'); const showAttr = getXAttrs(el, 'show')[0]; // If this is triggered by a x-show.transition. if (showAttr && showAttr.modifiers.includes('transition')) { let modifiers = showAttr.modifiers; // If x-show.transition.out, we'll skip the "in" transition. if (modifiers.includes('out') && !modifiers.includes('in')) return show(); const settingBothSidesOfTransition = modifiers.includes('in') && modifiers.includes('out'); // If x-show.transition.in...out... only use "in" related modifiers for this transition. modifiers = settingBothSidesOfTransition ? modifiers.filter((i, index) => index < modifiers.indexOf('out')) : modifiers; transitionHelperIn(el, modifiers, show); // Otherwise, we can assume x-transition:enter. } else if (attrs.length > 0) { transitionClassesIn(el, attrs, show); } else { // If neither, just show that damn thing. show(); } } function transitionOut(el, hide, forceSkip = false) { if (forceSkip) return hide(); const attrs = getXAttrs(el, 'transition'); const showAttr = getXAttrs(el, 'show')[0]; if (showAttr && showAttr.modifiers.includes('transition')) { let modifiers = showAttr.modifiers; if (modifiers.includes('in') && !modifiers.includes('out')) return hide(); const settingBothSidesOfTransition = modifiers.includes('in') && modifiers.includes('out'); modifiers = settingBothSidesOfTransition ? modifiers.filter((i, index) => index > modifiers.indexOf('out')) : modifiers; transitionHelperOut(el, modifiers, settingBothSidesOfTransition, hide); } else if (attrs.length > 0) { transitionClassesOut(el, attrs, hide); } else { hide(); } } function transitionHelperIn(el, modifiers, showCallback) { // Default values inspired by: https://material.io/design/motion/speed.html#duration const styleValues = { duration: modifierValue(modifiers, 'duration', 150), origin: modifierValue(modifiers, 'origin', 'center'), first: { opacity: 0, scale: modifierValue(modifiers, 'scale', 95) }, second: { opacity: 1, scale: 100 } }; transitionHelper(el, modifiers, showCallback, () => {}, styleValues); } function transitionHelperOut(el, modifiers, settingBothSidesOfTransition, hideCallback) { // Make the "out" transition .5x slower than the "in". (Visually better) // HOWEVER, if they explicitly set a duration for the "out" transition, // use that. const duration = settingBothSidesOfTransition ? modifierValue(modifiers, 'duration', 150) : modifierValue(modifiers, 'duration', 150) / 2; const styleValues = { duration: duration, origin: modifierValue(modifiers, 'origin', 'center'), first: { opacity: 1, scale: 100 }, second: { opacity: 0, scale: modifierValue(modifiers, 'scale', 95) } }; transitionHelper(el, modifiers, () => {}, hideCallback, styleValues); } function modifierValue(modifiers, key, fallback) { // If the modifier isn't present, use the default. if (modifiers.indexOf(key) === -1) return fallback; // If it IS present, grab the value after it: x-show.transition.duration.500ms const rawValue = modifiers[modifiers.indexOf(key) + 1]; if (!rawValue) return fallback; if (key === 'scale') { // Check if the very next value is NOT a number and return the fallback. // If x-show.transition.scale, we'll use the default scale value. // That is how a user opts out of the opacity transition. if (!isNumeric(rawValue)) return fallback; } if (key === 'duration') { // Support x-show.transition.duration.500ms && duration.500 let match = rawValue.match(/([0-9]+)ms/); if (match) return match[1]; } if (key === 'origin') { // Support chaining origin directions: x-show.transition.top.right if (['top', 'right', 'left', 'center', 'bottom'].includes(modifiers[modifiers.indexOf(key) + 2])) { return [rawValue, modifiers[modifiers.indexOf(key) + 2]].join(' '); } } return rawValue; } function transitionHelper(el, modifiers, hook1, hook2, styleValues) { // If the user set these style values, we'll put them back when we're done with them. const opacityCache = el.style.opacity; const transformCache = el.style.transform; const transformOriginCache = el.style.transformOrigin; // If no modifiers are present: x-show.transition, we'll default to both opacity and scale. const noModifiers = !modifiers.includes('opacity') && !modifiers.includes('scale'); const transitionOpacity = noModifiers || modifiers.includes('opacity'); const transitionScale = noModifiers || modifiers.includes('scale'); // These are the explicit stages of a transition (same stages for in and for out). // This way you can get a birds eye view of the hooks, and the differences // between them. const stages = { start() { if (transitionOpacity) el.style.opacity = styleValues.first.opacity; if (transitionScale) el.style.transform = `scale(${styleValues.first.scale / 100})`; }, during() { if (transitionScale) el.style.transformOrigin = styleValues.origin; el.style.transitionProperty = [transitionOpacity ? `opacity` : ``, transitionScale ? `transform` : ``].join(' ').trim(); el.style.transitionDuration = `${styleValues.duration / 1000}s`; el.style.transitionTimingFunction = `cubic-bezier(0.4, 0.0, 0.2, 1)`; }, show() { hook1(); }, end() { if (transitionOpacity) el.style.opacity = styleValues.second.opacity; if (transitionScale) el.style.transform = `scale(${styleValues.second.scale / 100})`; }, hide() { hook2(); }, cleanup() { if (transitionOpacity) el.style.opacity = opacityCache; if (transitionScale) el.style.transform = transformCache; if (transitionScale) el.style.transformOrigin = transformOriginCache; el.style.transitionProperty = null; el.style.transitionDuration = null; el.style.transitionTimingFunction = null; } }; transition(el, stages); } function transitionClassesIn(el, directives, showCallback) { const enter = (directives.find(i => i.value === 'enter') || { expression: '' }).expression.split(' ').filter(i => i !== ''); const enterStart = (directives.find(i => i.value === 'enter-start') || { expression: '' }).expression.split(' ').filter(i => i !== ''); const enterEnd = (directives.find(i => i.value === 'enter-end') || { expression: '' }).expression.split(' ').filter(i => i !== ''); transitionClasses(el, enter, enterStart, enterEnd, showCallback, () => {}); } function transitionClassesOut(el, directives, hideCallback) { const leave = (directives.find(i => i.value === 'leave') || { expression: '' }).expression.split(' ').filter(i => i !== ''); const leaveStart = (directives.find(i => i.value === 'leave-start') || { expression: '' }).expression.split(' ').filter(i => i !== ''); const leaveEnd = (directives.find(i => i.value === 'leave-end') || { expression: '' }).expression.split(' ').filter(i => i !== ''); transitionClasses(el, leave, leaveStart, leaveEnd, () => {}, hideCallback); } function transitionClasses(el, classesDuring, classesStart, classesEnd, hook1, hook2) { const originalClasses = el.__x_original_classes || []; const stages = { start() { el.classList.add(...classesStart); }, during() { el.classList.add(...classesDuring); }, show() { hook1(); }, end() { // Don't remove classes that were in the original class attribute. el.classList.remove(...classesStart.filter(i => !originalClasses.includes(i))); el.classList.add(...classesEnd); }, hide() { hook2(); }, cleanup() { el.classList.remove(...classesDuring.filter(i => !originalClasses.includes(i))); el.classList.remove(...classesEnd.filter(i => !originalClasses.includes(i))); } }; transition(el, stages); } function transition(el, stages) { stages.start(); stages.during(); requestAnimationFrame(() => { // Note: Safari's transitionDuration property will list out comma separated transition durations // for every single transition property. Let's grab the first one and call it a day. let duration = Number(getComputedStyle(el).transitionDuration.replace(/,.*/, '').replace('s', '')) * 1000; stages.show(); requestAnimationFrame(() => { stages.end(); setTimeout(() => { stages.hide(); // Adding an "isConnected" check, in case the callback // removed the element from the DOM. if (el.isConnected) { stages.cleanup(); } }, duration); }); }); } function isNumeric(subject) { return !isNaN(subject); } function handleForDirective(component, el, expression, initialUpdate, extraVars) { if (el.tagName.toLowerCase() !== 'template') console.warn('Alpine: [x-for] directive should only be added to