64 |
65 |
66 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignoreTestFiles": "*.html",
3 | "screenshotOnRunFailure": false,
4 | "video": false,
5 | "fixturesFolder": "tests/cypress/fixtures",
6 | "integrationFolder": "tests/cypress/integration",
7 | "pluginsFile": "tests/cypress/plugins/index.js",
8 | "screenshotsFolder": "tests/cypress/screenshots",
9 | "videosFolder": "tests/cypress/videos",
10 | "supportFile": "tests/cypress/support/index.js"
11 | }
12 |
--------------------------------------------------------------------------------
/editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | let config = {
2 | rootDir: './tests/jest',
3 | }
4 |
5 | module.exports = config
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "packages/*"
5 | ],
6 | "devDependencies": {
7 | "brotli-size": "^4.0.0",
8 | "cypress": "^5.5.0",
9 | "cypress-plugin-tab": "^1.0.5",
10 | "dot-json": "^1.2.2",
11 | "esbuild": "^0.8.39",
12 | "jest": "^26.6.3"
13 | },
14 | "scripts": {
15 | "build": "node ./scripts/build.js",
16 | "watch": "node ./scripts/build.js --watch",
17 | "test": "jest test && cypress run",
18 | "cypress": "cypress open",
19 | "jest": "jest test"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/alpinejs/builds/cdn.js:
--------------------------------------------------------------------------------
1 | import Alpine from './../src/index'
2 |
3 | window.Alpine = Alpine
4 |
5 | queueMicrotask(() => {
6 | Alpine.start()
7 | })
8 |
--------------------------------------------------------------------------------
/packages/alpinejs/builds/module.js:
--------------------------------------------------------------------------------
1 | import Alpine from './../src/index'
2 |
3 | export default Alpine
4 |
--------------------------------------------------------------------------------
/packages/alpinejs/node_modules/.package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "alpinejs",
3 | "version": "3.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "node_modules/@vue/reactivity": {
8 | "version": "3.0.11",
9 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.11.tgz",
10 | "integrity": "sha512-SKM3YKxtXHBPMf7yufXeBhCZ4XZDKP9/iXeQSC8bBO3ivBuzAi4aZi0bNoeE2IF2iGfP/AHEt1OU4ARj4ao/Xw==",
11 | "dependencies": {
12 | "@vue/shared": "3.0.11"
13 | }
14 | },
15 | "node_modules/@vue/shared": {
16 | "version": "3.0.11",
17 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.11.tgz",
18 | "integrity": "sha512-b+zB8A2so8eCE0JsxjL24J7vdGl8rzPQ09hZNhystm+KqSbKcAej1A+Hbva1rCMmTTqA+hFnUSDc5kouEo0JzA=="
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/alpinejs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "alpinejs",
3 | "version": "3.0.0-alpha.0",
4 | "description": "The rugged, minimal JavaScript framework",
5 | "author": "Caleb Porzio",
6 | "license": "MIT",
7 | "main": "dist/module.cjs.js",
8 | "module": "dist/module.esm.js",
9 | "dependencies": {
10 | "@vue/reactivity": "^3.0.2"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/alpine.js:
--------------------------------------------------------------------------------
1 | import { setReactivityEngine, reactive, effect, release, raw } from './reactivity'
2 | import { mapAttributes, directive, setPrefix as prefix } from './directives'
3 | import { setEvaluator, evaluate, evaluateLater } from './evaluator'
4 | import { start, addRootSelector, closestRoot } from './lifecycle'
5 | import { interceptor } from './interceptor'
6 | import { mutateDom } from './mutation'
7 | import { nextTick } from './nextTick'
8 | import { plugin } from './plugin'
9 | import { magic } from './magics'
10 | import { store } from './store'
11 | import { clone } from './clone'
12 | import { data } from './datas'
13 |
14 | let Alpine = {
15 | get reactive() { return reactive },
16 | get release() { return release },
17 | get effect() { return effect },
18 | get raw() { return raw },
19 | version: ALPINE_VERSION,
20 | setReactivityEngine,
21 | addRootSelector,
22 | mapAttributes,
23 | evaluateLater,
24 | setEvaluator,
25 | closestRoot,
26 | interceptor,
27 | mutateDom,
28 | directive,
29 | evaluate,
30 | nextTick,
31 | prefix,
32 | plugin,
33 | magic,
34 | store,
35 | start,
36 | clone,
37 | data,
38 | }
39 |
40 | export default Alpine
41 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/clone.js:
--------------------------------------------------------------------------------
1 | import { effect, release, overrideEffect } from "./reactivity"
2 | import { initTree, isRoot } from "./lifecycle"
3 | import { walk } from "./utils/walk"
4 |
5 | let isCloning = false
6 |
7 | export function skipDuringClone(callback) {
8 | return (...args) => isCloning || callback(...args)
9 | }
10 |
11 | export function onlyDuringClone(callback) {
12 | return (...args) => isCloning && callback(...args)
13 | }
14 |
15 | export function skipWalkingSubClone(callback) {
16 | return (...args) => isCloning || callback(...args)
17 | }
18 |
19 | export function interuptCrawl(callback) {
20 | return (...args) => isCloning || callback(...args)
21 | }
22 |
23 | export function clone(oldEl, newEl) {
24 | newEl._x_dataStack = oldEl._x_dataStack
25 |
26 | isCloning = true
27 |
28 | dontRegisterReactiveSideEffects(() => {
29 | cloneTree(newEl)
30 | })
31 |
32 | isCloning = false
33 | }
34 |
35 | export function cloneTree(el) {
36 | let hasRunThroughFirstEl = false
37 |
38 | let shallowWalker = (el, callback) => {
39 | walk(el, (el, skip) => {
40 | if (hasRunThroughFirstEl && isRoot(el)) return skip()
41 |
42 | hasRunThroughFirstEl = true
43 |
44 | callback(el, skip)
45 | })
46 | }
47 |
48 | initTree(el, shallowWalker)
49 | }
50 |
51 | function dontRegisterReactiveSideEffects(callback) {
52 | let cache = effect
53 |
54 | overrideEffect((callback, el) => {
55 | let storedEffect = cache(callback)
56 |
57 | release(storedEffect)
58 | })
59 |
60 | callback()
61 |
62 | overrideEffect(cache)
63 | }
64 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/datas.js:
--------------------------------------------------------------------------------
1 |
2 | let datas = {}
3 |
4 | export function data(name, callback) {
5 | datas[name] = callback
6 | }
7 |
8 | export function getNamedDataProvider(name) {
9 | return datas[name]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/index.js:
--------------------------------------------------------------------------------
1 | import './x-transition'
2 | import './x-ignore'
3 | import './x-effect'
4 | import './x-model'
5 | import './x-cloak'
6 | import './x-init'
7 | import './x-text'
8 | import './x-bind'
9 | import './x-data'
10 | import './x-show'
11 | import './x-for'
12 | import './x-ref'
13 | import './x-if'
14 | import './x-on'
15 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-bind.js:
--------------------------------------------------------------------------------
1 | import { directive, directives, into, mapAttributes, prefix, startingWith } from '../directives'
2 | import { evaluateLater } from '../evaluator'
3 | import { mutateDom } from '../mutation'
4 | import bind from '../utils/bind'
5 |
6 | mapAttributes(startingWith(':', into(prefix('bind:'))))
7 |
8 | directive('bind', (el, { value, modifiers, expression, original }, { effect }) => {
9 | if (! value) return applyBindingsObject(el, expression, original, effect)
10 |
11 | if (value === 'key') return storeKeyForXFor(el, expression)
12 |
13 | let evaluate = evaluateLater(el, expression)
14 |
15 | effect(() => evaluate(result => {
16 | mutateDom(() => bind(el, value, result, modifiers))
17 | }))
18 | })
19 |
20 | function applyBindingsObject(el, expression, original, effect) {
21 | let getBindings = evaluateLater(el, expression)
22 |
23 | let cleanupRunners = []
24 |
25 | effect(() => {
26 | while (cleanupRunners.length) cleanupRunners.pop()()
27 |
28 | getBindings(bindings => {
29 | let attributes = Object.entries(bindings).map(([name, value]) => ({ name, value }))
30 |
31 | directives(el, attributes, original).map(handle => {
32 | cleanupRunners.push(handle.runCleanups)
33 |
34 | handle()
35 | })
36 | })
37 |
38 | })
39 | }
40 |
41 | function storeKeyForXFor(el, expression) {
42 | el._x_key_expression = expression
43 | }
44 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-cloak.js:
--------------------------------------------------------------------------------
1 | import { directive, prefix } from '../directives'
2 | import { mutateDom } from '../mutation'
3 | import { nextTick } from '../nextTick'
4 |
5 | directive('cloak', el => nextTick(() => mutateDom(() => el.removeAttribute(prefix('cloak')))))
6 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-data.js:
--------------------------------------------------------------------------------
1 | import { directive, prefix } from '../directives'
2 | import { initInterceptors } from '../interceptor'
3 | import { getNamedDataProvider } from '../datas'
4 | import { addRootSelector } from '../lifecycle'
5 | import { skipDuringClone } from '../clone'
6 | import { addScopeToNode } from '../scope'
7 | import { injectMagics } from '../magics'
8 | import { reactive } from '../reactivity'
9 | import { evaluate } from '../evaluator'
10 |
11 | addRootSelector(() => `[${prefix('data')}]`)
12 |
13 | directive('data', skipDuringClone((el, { expression }, { cleanup }) => {
14 | expression = expression === '' ? '{}' : expression
15 |
16 | let dataProvider = getNamedDataProvider(expression)
17 |
18 | let data = {}
19 |
20 | if (dataProvider) {
21 | let magics = injectMagics({}, el)
22 |
23 | data = dataProvider.bind(magics)()
24 | } else {
25 | data = evaluate(el, expression)
26 | }
27 |
28 | initInterceptors(data)
29 |
30 | injectMagics(data, el)
31 |
32 | let reactiveData = reactive(data)
33 |
34 | let undo = addScopeToNode(el, reactiveData)
35 |
36 | if (reactiveData['init']) reactiveData['init']()
37 |
38 | cleanup(() => {
39 | undo()
40 |
41 | reactiveData['destroy'] && reactiveData['destroy']()
42 | })
43 | }))
44 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-effect.js:
--------------------------------------------------------------------------------
1 | import { directive } from '../directives'
2 | import { evaluateLater } from '../evaluator'
3 |
4 | directive('effect', (el, { expression }, { effect }) => effect(evaluateLater(el, expression)))
5 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-if.js:
--------------------------------------------------------------------------------
1 | import { evaluateLater } from '../evaluator'
2 | import { setStyles } from '../utils/styles'
3 | import { directive } from '../directives'
4 | import { once } from '../utils/once'
5 |
6 | directive('if', (el, { modifiers, expression }, { effect, cleanup }) => {
7 | let evaluate = evaluateLater(el, expression)
8 |
9 | let show = () => {
10 | if (el._x_currentIfEl) return el._x_currentIfEl
11 |
12 | let clone = el.content.cloneNode(true).firstElementChild
13 |
14 | el.after(clone)
15 |
16 | el._x_currentIfEl = clone
17 |
18 | el._x_undoIf = () => { clone.remove(); delete el._x_currentIfEl }
19 |
20 | return clone
21 | }
22 |
23 | let hide = () => el._x_undoIf?.() || delete el._x_undoIf
24 |
25 | effect(() => evaluate(value => {
26 | value ? show() : hide()
27 | }))
28 |
29 | cleanup(() => el._x_undoIf && el._x_undoIf())
30 | })
31 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-ignore.js:
--------------------------------------------------------------------------------
1 | import { directive } from "../directives"
2 |
3 | let handler = () => {}
4 |
5 | handler.inline = (el, { modifiers }, { cleanup }) => {
6 | modifiers.includes('self')
7 | ? el._x_ignore_self = true
8 | : el._x_ignore = true
9 |
10 | cleanup(() => {
11 | modifiers.includes('self')
12 | ? delete el._x_ignore_self
13 | : delete el._x_ignore
14 | })
15 | }
16 |
17 | directive('ignore', handler)
18 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-init.js:
--------------------------------------------------------------------------------
1 | import { directive, prefix } from "../directives";
2 | import { addRootSelector } from "../lifecycle";
3 | import { skipDuringClone } from "../clone";
4 | import { evaluate } from "../evaluator";
5 |
6 | addRootSelector(() => `[${prefix('init')}]`)
7 |
8 | directive('init', skipDuringClone((el, { expression }) => evaluate(el, expression, {}, false)))
9 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-model.js:
--------------------------------------------------------------------------------
1 | import { evaluateLater } from '../evaluator'
2 | import { directive } from '../directives'
3 | import { mutateDom } from '../mutation'
4 | import bind from '../utils/bind'
5 | import on from '../utils/on'
6 |
7 | directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
8 | let evaluate = evaluateLater(el, expression)
9 | let assignmentExpression = `${expression} = rightSideOfExpression($event, ${expression})`
10 | let evaluateAssignment = evaluateLater(el, assignmentExpression)
11 |
12 | // If the element we are binding to is a select, a radio, or checkbox
13 | // we'll listen for the change event instead of the "input" event.
14 | var event = (el.tagName.toLowerCase() === 'select')
15 | || ['checkbox', 'radio'].includes(el.type)
16 | || modifiers.includes('lazy')
17 | ? 'change' : 'input'
18 |
19 | let assigmentFunction = generateAssignmentFunction(el, modifiers, expression)
20 |
21 | let removeListener = on(el, event, modifiers, (e) => {
22 | evaluateAssignment(() => {}, { scope: {
23 | '$event': e,
24 | rightSideOfExpression: assigmentFunction
25 | }})
26 | })
27 |
28 | cleanup(() => removeListener())
29 |
30 | el._x_forceModelUpdate = () => {
31 | evaluate(value => {
32 | // If nested model key is undefined, set the default value to empty string.
33 | if (value === undefined && expression.match(/\./)) value = ''
34 |
35 | // @todo: This is nasty
36 | window.fromModel = true
37 | mutateDom(() => bind(el, 'value', value))
38 | delete window.fromModel
39 | })
40 | }
41 |
42 | effect(() => {
43 | // Don't modify the value of the input if it's focused.
44 | if (modifiers.includes('unintrusive') && document.activeElement.isSameNode(el)) return
45 |
46 | el._x_forceModelUpdate()
47 | })
48 | })
49 |
50 | function generateAssignmentFunction(el, modifiers, expression) {
51 | if (el.type === 'radio') {
52 | // Radio buttons only work properly when they share a name attribute.
53 | // People might assume we take care of that for them, because
54 | // they already set a shared "x-model" attribute.
55 | mutateDom(() => {
56 | if (! el.hasAttribute('name')) el.setAttribute('name', expression)
57 | })
58 | }
59 |
60 | return (event, currentValue) => {
61 | return mutateDom(() => {
62 | // Check for event.detail due to an issue where IE11 handles other events as a CustomEvent.
63 | if (event instanceof CustomEvent && event.detail !== undefined) {
64 | return event.detail
65 | } else if (el.type === 'checkbox') {
66 | // If the data we are binding to is an array, toggle its value inside the array.
67 | if (Array.isArray(currentValue)) {
68 | let newValue = modifiers.includes('number') ? safeParseNumber(event.target.value) : event.target.value
69 |
70 | return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(el => ! checkedAttrLooseCompare(el, newValue))
71 | } else {
72 | return event.target.checked
73 | }
74 | } else if (el.tagName.toLowerCase() === 'select' && el.multiple) {
75 | return modifiers.includes('number')
76 | ? Array.from(event.target.selectedOptions).map(option => {
77 | let rawValue = option.value || option.text
78 | return safeParseNumber(rawValue)
79 | })
80 | : Array.from(event.target.selectedOptions).map(option => {
81 | return option.value || option.text
82 | })
83 | } else {
84 | let rawValue = event.target.value
85 | return modifiers.includes('number')
86 | ? safeParseNumber(rawValue)
87 | : (modifiers.includes('trim') ? rawValue.trim() : rawValue)
88 | }
89 | })
90 | }
91 | }
92 |
93 | function safeParseNumber(rawValue) {
94 | let number = rawValue ? parseFloat(rawValue) : null
95 |
96 | return isNumeric(number) ? number : rawValue
97 | }
98 |
99 | function checkedAttrLooseCompare(valueA, valueB) {
100 | return valueA == valueB
101 | }
102 |
103 | function isNumeric(subject){
104 | return ! Array.isArray(subject) && ! isNaN(subject)
105 | }
106 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-on.js:
--------------------------------------------------------------------------------
1 | import { directive, into, mapAttributes, prefix, startingWith } from '../directives'
2 | import { evaluateLater } from '../evaluator'
3 | import { skipDuringClone } from '../clone'
4 | import on from '../utils/on'
5 |
6 | mapAttributes(startingWith('@', into(prefix('on:'))))
7 |
8 | directive('on', skipDuringClone((el, { value, modifiers, expression }, { cleanup }) => {
9 | let evaluate = expression ? evaluateLater(el, expression) : () => {}
10 |
11 | let removeListener = on(el, value, modifiers, e => {
12 | evaluate(() => {}, { scope: { '$event': e }, params: [e] })
13 | })
14 |
15 | cleanup(() => removeListener())
16 | }))
17 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-ref.js:
--------------------------------------------------------------------------------
1 | import { closestRoot } from '../lifecycle'
2 | import { directive } from '../directives'
3 |
4 | function handler () {}
5 |
6 | handler.inline = (el, { expression }, { cleanup }) => {
7 | let root = closestRoot(el)
8 |
9 | if (! root._x_refs) root._x_refs = {}
10 |
11 | root._x_refs[expression] = el
12 |
13 | cleanup(() => delete root._x_refs[expression])
14 | }
15 |
16 | directive('ref', handler)
17 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-show.js:
--------------------------------------------------------------------------------
1 | import { evaluateLater } from '../evaluator'
2 | import { directive } from '../directives'
3 | import { mutateDom } from '../mutation'
4 | import { once } from '../utils/once'
5 |
6 | directive('show', (el, { modifiers, expression }, { effect }) => {
7 | let evaluate = evaluateLater(el, expression)
8 |
9 | let hide = () => mutateDom(() => {
10 | el.style.display = 'none'
11 |
12 | el._x_is_shown = false
13 | })
14 |
15 | let show = () => mutateDom(() => {
16 | if (el.style.length === 1 && el.style.display === 'none') {
17 | el.removeAttribute('style')
18 | } else {
19 | el.style.removeProperty('display')
20 | }
21 |
22 | el._x_is_shown = true
23 | })
24 |
25 | // We are wrapping this function in a setTimeout here to prevent
26 | // a race condition from happening where elements that have a
27 | // @click.away always view themselves as shown on the page.
28 | let clickAwayCompatibleShow = () => setTimeout(show)
29 |
30 | let toggle = once(
31 | value => value ? show() : hide(),
32 | value => {
33 | if (typeof el._x_toggleAndCascadeWithTransitions === 'function') {
34 | el._x_toggleAndCascadeWithTransitions(el, value, show, hide)
35 | } else {
36 | value ? clickAwayCompatibleShow() : hide()
37 | }
38 | }
39 | )
40 |
41 | effect(() => evaluate(value => {
42 | if (modifiers.includes('immediate')) value ? clickAwayCompatibleShow() : hide()
43 |
44 | toggle(value)
45 | }))
46 | })
47 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/directives/x-text.js:
--------------------------------------------------------------------------------
1 | import { evaluateLater } from '../evaluator'
2 | import { directive } from '../directives'
3 | import { mutateDom } from '../mutation'
4 |
5 | directive('text', (el, { expression }, { effect, cleanup }) => {
6 | let evaluate = evaluateLater(el, expression)
7 |
8 | effect(() => {
9 | evaluate(value => {
10 | mutateDom(() => {
11 | el.textContent = value
12 | })
13 | })
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/evaluator.js:
--------------------------------------------------------------------------------
1 | import { closestDataStack, mergeProxies } from './scope'
2 | import { injectMagics } from './magics'
3 |
4 | export function evaluate(el, expression, extras = {}) {
5 | let result
6 |
7 | evaluateLater(el, expression)(value => result = value, extras)
8 |
9 | return result
10 | }
11 |
12 | export function evaluateLater(...args) {
13 | return theEvaluatorFunction(...args)
14 | }
15 |
16 | let theEvaluatorFunction = normalEvaluator
17 |
18 | export function setEvaluator(newEvaluator) {
19 | theEvaluatorFunction = newEvaluator
20 | }
21 |
22 | export function normalEvaluator(el, expression) {
23 | let overriddenMagics = {}
24 |
25 | injectMagics(overriddenMagics, el)
26 |
27 | let dataStack = [overriddenMagics, ...closestDataStack(el)]
28 |
29 | if (typeof expression === 'function') {
30 | return generateEvaluatorFromFunction(dataStack, expression)
31 | }
32 |
33 | let evaluator = generateEvaluatorFromString(dataStack, expression)
34 |
35 | return tryCatch.bind(null, el, expression, evaluator)
36 | }
37 |
38 | export function generateEvaluatorFromFunction(dataStack, func) {
39 | return (receiver = () => {}, { scope = {}, params = [] } = {}) => {
40 | let result = func.apply(mergeProxies([scope, ...dataStack]), params)
41 |
42 | runIfTypeOfFunction(receiver, result)
43 | }
44 | }
45 |
46 | let evaluatorMemo = {}
47 |
48 | function generateFunctionFromString(expression) {
49 | if (evaluatorMemo[expression]) {
50 | return evaluatorMemo[expression]
51 | }
52 |
53 | let AsyncFunction = Object.getPrototypeOf(async function(){}).constructor
54 |
55 | // Some expressions that are useful in Alpine are not valid as the right side of an expression.
56 | // Here we'll detect if the expression isn't valid for an assignement and wrap it in a self-
57 | // calling function so that we don't throw an error AND a "return" statement can b e used.
58 | let rightSideSafeExpression = 0
59 | // Support expressions starting with "if" statements like: "if (...) doSomething()"
60 | || /^[\n\s]*if.*\(.*\)/.test(expression)
61 | // Support expressions starting with "let/const" like: "let foo = 'bar'"
62 | || /^(let|const)/.test(expression)
63 | ? `(() => { ${expression} })()`
64 | : expression
65 |
66 | let func = new AsyncFunction(['__self', 'scope'], `with (scope) { __self.result = ${rightSideSafeExpression} }; __self.finished = true; return __self.result;`)
67 |
68 | evaluatorMemo[expression] = func
69 |
70 | return func
71 | }
72 |
73 | function generateEvaluatorFromString(dataStack, expression) {
74 | let func = generateFunctionFromString(expression)
75 |
76 | return (receiver = () => {}, { scope = {}, params = [] } = {}) => {
77 | func.result = undefined
78 | func.finished = false
79 |
80 | // Run the function.
81 |
82 | let completeScope = mergeProxies([ scope, ...dataStack ])
83 |
84 | let promise = func(func, completeScope)
85 |
86 | // Check if the function ran synchronously,
87 | if (func.finished) {
88 | // Return the immediate result.
89 | runIfTypeOfFunction(receiver, func.result, completeScope, params)
90 | } else {
91 | // If not, return the result when the promise resolves.
92 | promise.then(result => {
93 | runIfTypeOfFunction(receiver, result, completeScope, params)
94 | })
95 | }
96 | }
97 | }
98 |
99 | export function runIfTypeOfFunction(receiver, value, scope, params) {
100 | if (typeof value === 'function') {
101 | let result = value.apply(scope, params)
102 |
103 | if (result instanceof Promise) {
104 | result.then(i => runIfTypeOfFunction(receiver, i, scope, params))
105 | } else {
106 | receiver(result)
107 | }
108 | } else {
109 | receiver(value)
110 | }
111 | }
112 |
113 | export function tryCatch(el, expression, callback, ...args) {
114 | try {
115 | return callback(...args)
116 | } catch (e) {
117 | console.warn(`Alpine Expression Error: ${e.message}\n\nExpression: "${expression}"\n\n`, el)
118 |
119 | throw e
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * _
3 | * /\ | | (_) (_)
4 | * / \ | |_ __ _ _ __ ___ _ ___
5 | * / /\ \ | | '_ \| | '_ \ / _ \ | / __|
6 | * / ____ \| | |_) | | | | | __/_| \__ \
7 | * /_/ \_\_| .__/|_|_| |_|\___(_) |___/
8 | * | | _/ |
9 | * |_| |__/
10 | *
11 | * Let's build Alpine together. It's easier than you think.
12 | * For starters, we'll import Alpine's core. This is the
13 | * object that will expose all of Alpine's public API.
14 | */
15 | import Alpine from './alpine'
16 |
17 | /**
18 | * _______________________________________________________
19 | * The Evaluator
20 | * -------------------------------------------------------
21 | *
22 | * Now we're ready to bootstrap Alpine's evaluation system.
23 | * It's the function that converts raw JavaScript string
24 | * expressions like @click="toggle()", into actual JS.
25 | */
26 | import { normalEvaluator } from './evaluator'
27 |
28 | Alpine.setEvaluator(normalEvaluator)
29 |
30 | /**
31 | * _______________________________________________________
32 | * The Reactivity Engine
33 | * -------------------------------------------------------
34 | *
35 | * This is the reactivity core of Alpine. It's the part of
36 | * Alpine that triggers an element with x-text="message"
37 | * to update its inner text when "message" is changed.
38 | */
39 | import { reactive, effect, stop, toRaw } from '@vue/reactivity'
40 |
41 | Alpine.setReactivityEngine({ reactive, effect, release: stop, raw: toRaw })
42 |
43 | /**
44 | * _______________________________________________________
45 | * The Magics
46 | * -------------------------------------------------------
47 | *
48 | * Yeah, we're calling them magics here like they're nouns.
49 | * These are the properties that are magically available
50 | * to all the Alpine expressions, within your web app.
51 | */
52 | import './magics/index'
53 |
54 | /**
55 | * _______________________________________________________
56 | * The Directives
57 | * -------------------------------------------------------
58 | *
59 | * Now that the core is all set up, we can register Alpine
60 | * directives like x-text or x-on that form the basis of
61 | * how Alpine adds behavior to an app's static markup.
62 | */
63 | import './directives/index'
64 |
65 | /**
66 | * _______________________________________________________
67 | * The Alpine Global
68 | * -------------------------------------------------------
69 | *
70 | * Now that we have set everything up internally, anything
71 | * Alpine-related that will need to be accessed on-going
72 | * will be made available through the "Alpine" global.
73 | */
74 | export default Alpine
75 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/interceptor.js:
--------------------------------------------------------------------------------
1 | export function initInterceptors(data) {
2 | let isObject = val => typeof val === 'object' && !Array.isArray(val) && val !== null
3 |
4 | let recurse = (obj, basePath = '') => {
5 | Object.entries(obj).forEach(([key, value]) => {
6 | let path = basePath === '' ? key : `${basePath}.${key}`
7 |
8 | if (typeof value === 'function' && value.interceptor) {
9 | let result = value(key, path)
10 |
11 | Object.defineProperty(obj, key, result[0])
12 | }
13 |
14 | if (isObject(value)) {
15 | recurse(value, path)
16 | }
17 | })
18 | }
19 |
20 | return recurse(data)
21 | }
22 |
23 | export function interceptor(callback, mutateFunc = () => {}) {
24 | return initialValue => {
25 | function func(key, path) {
26 | let parentFunc = func.parent
27 | ? func.parent
28 | : (key, path) => ([{}, { initer() {}, setter() {} }])
29 |
30 | let [parentNoop, { initer: parentIniter, setter: parentSetter, initialValue: parentInitialValue }] = parentFunc(key, path)
31 |
32 | let store = parentInitialValue === undefined ? initialValue : parentInitialValue
33 |
34 | let { init: initer, set: setter } = callback(key, path)
35 |
36 | let inited = false
37 |
38 | let setValue = i => store = i
39 | let reactiveSetValue = function (i) { this[key] = i }
40 |
41 | let setup = (context) => {
42 | if (inited) return
43 |
44 | parentIniter.bind(context)(store, setValue, reactiveSetValue.bind(context))
45 | initer.bind(context)(store, setValue, reactiveSetValue.bind(context))
46 |
47 | inited = true
48 | }
49 |
50 | return [{
51 | get() {
52 | setup(this)
53 |
54 | return store
55 | },
56 | set(value) {
57 | setup(this)
58 |
59 | parentSetter.bind(this)(value, setValue, reactiveSetValue.bind(this))
60 | setter.bind(this)(value, setValue, reactiveSetValue.bind(this))
61 | },
62 | enumerable: true,
63 | configurable: true,
64 | }, { initer, setter, initialValue }]
65 | }
66 |
67 | func.interceptor = true
68 |
69 | mutateFunc(func)
70 |
71 | if (typeof initialValue === 'function' && initialValue.interceptor) {
72 | func.parent = initialValue
73 | }
74 |
75 | return func
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/packages/alpinejs/src/lifecycle.js:
--------------------------------------------------------------------------------
1 | import { startObservingMutations, onAttributesAdded, onElAdded, onElRemoved } from "./mutation"
2 | import { deferHandlingDirectives, directives } from "./directives"
3 | import { dispatch } from './utils/dispatch'
4 | import { nextTick } from "./nextTick"
5 | import { walk } from "./utils/walk"
6 | import { warn } from './utils/warn'
7 |
8 | export function start() {
9 | if (! document.body) warn('Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine\'s `
25 |
26 | ```
27 |
28 |
29 | ### Module import
30 |
31 | ```js
32 | import Alpine from '@alpinejs/csp'
33 |
34 | window.Alpine = Alpine
35 | window.Alpine.start()
36 | ```
37 |
38 |
39 | ## Restrictions
40 |
41 | Because Alpine can no longer interpret strings as plain JavaScript, it has to parse and construct JavaScript functions from them manually.
42 |
43 | Because of this limitation, you must `Alpine.data` to register your `x-data` objects, and must reference properties and methods from it by key only.
44 |
45 | For example, an inline component like this will not work.
46 |
47 | ```html
48 |
49 |
50 |
51 |
52 |
53 |
54 | ```
55 |
56 | However, breaking out the expressions into external APIs, the following is valid with the CSP build:
57 |
58 | ```html
59 |
60 |
61 |
62 |
63 |
64 |
65 | ```
66 | ```js
67 | Alpine.data('counter', () => ({
68 | count: 1,
69 |
70 | increment() { this.count++ }
71 | }))
72 | ```
73 |
--------------------------------------------------------------------------------
/packages/docs/src/en/advanced/reactivity.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: Reactivity
4 | ---
5 |
6 | # Reactivity
7 |
8 | Alpine is "reactive" in the sense that when you change a peice of data, everything that depends on that data "reacts" automatically to that change.
9 |
10 | Every bit of reactivity that takes place in Alpine, happens because of two very important reactive functions in Alpine's core: `Alpine.reactive()`, and `Alpine.effect()`.
11 |
12 | > Alpine uses VueJS's reactivity engine under the hood to provide these functions.
13 | > [→ Read more about @vue/reactivity](https://github.com/vuejs/vue-next/tree/master/packages/reactivity)
14 |
15 | Understanding these two functions will give you super powers as an Alpine developer, but also just as a web developer in general.
16 |
17 |
18 | ## Alpine.reactive()
19 |
20 | Let's first look at `Alpine.reactive()`. This function accepts a JavaScript object as its parameter and returns a "reactive" version of that object. For example:
21 |
22 | ```js
23 | let data = { count: 1 }
24 |
25 | let reactiveData = Alpine.reactive(data)
26 | ```
27 |
28 | Under the hood, when `Alpine.reactive` receives `data`, it wraps it inside a custom JavaScript proxy.
29 |
30 | A proxy is a special kind of object in JavaScript that can intercept "get" and "set" calls to a JavaScript objct.
31 |
32 | [→ Read more about JavaScript proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
33 |
34 | At face value, `reactiveData` should behave exactly like `data`. For example:
35 |
36 | ```js
37 | console.log(data.count) // 1
38 | console.log(reactiveData.count) // 1
39 |
40 | reactiveData.count = 2
41 |
42 | console.log(data.count) // 2
43 | console.log(reactiveData.count) // 2
44 | ```
45 |
46 | What you see here is that because `reactiveData` is a thin wrapper around `data`, any attempts to get or set a property will behave exactly as if you had interacted with `data` directly.
47 |
48 | The main difference here is that any time you modify or retreive (get or set) a value from `reactiveData`, Alpine is aware of it and can execute any other logic that depends on this data.
49 |
50 | `Alpine.reactive` is only the first half of the story. `Alpine.effect` is the other half, let's dig in.
51 |
52 |
53 | ## Alpine.effect()
54 |
55 | `Alpine.effect` accepts a single callback function. As soon as `Alpine.effect` is called, it will run the provided function, but actively look for any interactions with reactive data. If it detects an interaction (a get or set from the aforementioned reactive proxy) it will keep track of it and make sure to re-run the callback if any of reactive data changes in the future. For example:
56 |
57 | ```js
58 | let data = Alpine.reactive({ count: 1 })
59 |
60 | Alpine.effect(() => {
61 | console.log(data.count)
62 | })
63 | ```
64 |
65 | When this code is firt run, "1" will be logged to the console. Any time `data.count` changes, it's value will be logged to the console again.
66 |
67 | This is the mechanism that unlocks all of the reactivity at the core of Alpine.
68 |
69 | To connect the dots further, let's look at a simple "counter" component example without using Alpine syntax at all, only using `Alpine.reactive` and `Alpine.effect`:
70 |
71 | ```html
72 |
73 |
74 | Count:
75 | ```
76 | ```js
77 | let button = document.querySelector('button')
78 | let span = document.querySelector('span')
79 |
80 | let data = Alpine.reactive({ count: 1 })
81 |
82 | Alpine.effect(() => {
83 | span.textContent = data.count
84 | })
85 |
86 | button.addEventListener('click', () => {
87 | data.count = data.count + 1
88 | })
89 | ```
90 |
91 |
92 |
93 |
94 |
95 |
Count:
96 |
97 |
98 |
99 | As you can see, you can make any data reactive, and you can also wrap any functionality in `Alpine.effect`.
100 |
101 | This combination unlocks an incredibly powerful programming paradaigm for web development. Run wild and free.
102 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 4
3 | title: Directives
4 | prefix: x-
5 | font-type: mono
6 | type: sub-directory
7 | ---
8 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/cloak.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 12
3 | title: cloak
4 | ---
5 |
6 | # `x-cloak`
7 |
8 | Sometimes, when you're using AlpineJS for a part of your template, there is a "blip" where you might see your uninitialized template after the page loads, but before Alpine loads.
9 |
10 | `x-cloak` addresses this scenerio by hiding the element it's attached to until Alpine is fully loaded on the page.
11 |
12 | For `x-cloak` to work however, you must add the following CSS to the page.
13 |
14 | ```css
15 | [x-cloak] { display: none !important; }
16 | ```
17 |
18 | Now, the following example will hide the `` tag until Alpine has set its text content to the `message` property.
19 |
20 | ```html
21 |
22 | ```
23 |
24 | When Alpine loads on the page, it removes all `x-cloak` property from the element, which also removes the `display: none;` applied by CSS, therefore showing the element.
25 |
26 | If you'd like to achieve this same behavior, but avoid having to include a global style, you can use the following cool, but admittadly odd trick:
27 |
28 | ```html
29 |
30 |
31 |
32 | ```
33 |
34 | This will achieve the same goal as `x-cloak` by just leveraging the way `x-if` works.
35 |
36 | Because `` elements are "hidden" in browsers by default, you won't see the `` until Alpine has had a chance to render the `x-if="true"` and show it.
37 |
38 | Again, this solution is not for everyone, but it's worth mentioning for special cases.
39 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/effect.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 11
3 | title: effect
4 | ---
5 |
6 | # `x-effect`
7 |
8 | `x-effect` is a useful directive for re-evaluating an expression when one of its dependancies change. You can think of it as a watcher where you don't have to specify what property to watch, it will watch all properties used within it.
9 |
10 | If this definition is confusing for you, that's ok. It's better explained through an example:
11 |
12 | ```html
13 |
14 |
15 |
16 | ```
17 |
18 | When this component is loaded, the `x-effect` expression will be run and "Hello" will be logged into the console.
19 |
20 | Because Alpine knows about any property references contained within `x-effect`, when the button is clicked and `label` is changed", the effect will be retriggered and "Hello World!" will be logged to the console.
21 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/for.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 8
3 | title: for
4 | ---
5 |
6 | # `x-for`
7 |
8 | Alpine's `x-for` directive allows you to create DOM elements by iterating through a list. Here's a simple example of using it to create a list of colors based on an array.
9 |
10 | ```html
11 |
12 |
13 |
14 |
15 |
16 | ```
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | There are two rules worth noting about `x-for`:
29 |
30 | * `x-for` MUST be declared on a `` element
31 | * That `` element MUST have only one root element
32 |
33 |
34 | ## Keys
35 |
36 | It is important to specificy keys for each `x-for` iteration if you are going to be re-ordering items. Without dynamic keys, Alpine may have a hard time keeping track of what re-orders and will cause odd side-effects.
37 |
38 | ```html
39 |
44 |
45 |
46 |
47 |
48 | ```
49 |
50 | Now if the colors are added, removed, re-ordered, or their "id"s change, Alpine will preserve or destroy the iterated `
`elements accordingly.
51 |
52 |
53 | ## Accessing indexes
54 |
55 | If you need to access the index of each item in the iteration, you can do so using the `([item], [index]) in [items]` syntax like so:
56 |
57 | ```html
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | ```
67 |
68 | You can also access the index inside a dynamic `:key` expression.
69 |
70 | ```html
71 |
72 | ```
73 |
74 |
75 | ## Iterating over a range
76 |
77 | If you need to simply loop `n` number of times, rather than iterate through an array, Alpine offers a short syntax.
78 |
79 | ```html
80 |
81 |
82 |
83 |
84 |
85 | ```
86 |
87 | `i` in this case can be named anything you like.
88 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/if.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 16
3 | title: if
4 | ---
5 |
6 | # `x-if`
7 |
8 | `x-if` is used for toggling elements on the page, similarly to `x-show`, however it completely adds and removes the elememt it's applied to rather than just changing its CSS display property to "hidden".
9 |
10 | Because of this difference in behavior, `x-if` should not be applied directly to the element, but instead to a `` tag that encloses the element. This way, Alpine can keep a record of the element once it's removed from the page.
11 |
12 | ```html
13 |
14 |
Contents...
15 |
16 | ```
17 |
18 | > Unlike `x-show`, `x-if`, does NOT support tansitioning toggles with `x-transition`.
19 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/ignore.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 11
3 | title: ignore
4 | ---
5 |
6 | # `x-ignore`
7 |
8 | By default, Alpine will crawl and initialize the entire DOM tree of an element containing `x-init` or `x-data`.
9 |
10 | If for some reason, you don't want Alpine to touch a specific section of your HTML, you can prevent it from doing so using `x-ignore`.
11 |
12 | ```html
13 |
14 |
15 |
16 |
17 |
18 | ```
19 |
20 | In the above example, the `` tag will not contain "From Alpine" because we told Alpine to ignore the contents of the `div` completely.
21 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/init.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: init
4 | ---
5 |
6 | # `x-init`
7 |
8 | The `x-init` directive allows you to hook into the initialization phase of any element in Alpine.
9 |
10 | ```html
11 |
12 | ```
13 |
14 | In the above example, "I\'m being initialized!" will be output in the console before it makes further DOM updates.
15 |
16 | Consider another example where `x-init` is used to fetch some JSON and store it in `x-data` before the component is processed.
17 |
18 | ```html
19 |
...
23 | ```
24 |
25 |
26 | ## $nextTick
27 |
28 | Sometimes, you want to wait until after Alpine has completely finished rendering to execute some code.
29 |
30 | This would be something like `useEffect(..., [])` in react, or `mount` in Vue.
31 |
32 | By using Alpine's internal `$nextTick` magic, you can make this happen.
33 |
34 | ```html
35 |
36 | ```
37 |
38 |
39 | ## Standalone `x-init`
40 |
41 | You can add `x-init` to any elements inside or outside an `x-data` HTML block. For example:
42 |
43 | ```html
44 |
45 |
46 |
47 |
48 |
49 | ```
50 |
51 |
52 | ## Auto-evaluate init() method
53 |
54 | If the `x-data` object of a component contains an `init()` method, it will be called automatically. For example:
55 |
56 | ```html
57 |
62 | ...
63 |
64 | ```
65 |
66 | This is also the case for components that were registered using the `Alpine.data()` syntax.
67 |
68 | ```js
69 | Alpine.data('dropdown', () => ({
70 | init() {
71 | console.log('I will get evaluated when initializing each "dropdown" component.')
72 | },
73 | }))
74 | ```
75 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/ref.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 11
3 | title: ref
4 | ---
5 |
6 | # `x-ref`
7 |
8 | `x-ref` in combination with `$refs` is a useful utility for easily accessing DOM elements directly. It's most useful as a replacement for APIs like `getElementById` and `querySelector`.
9 |
10 | ```html
11 |
12 |
13 | Hello 👋
14 | ```
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Hello 👋
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/show.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: show
4 | ---
5 |
6 | # `x-show`
7 |
8 | `x-show` is one of the most useful and powerful directives in Alpine. It provides an expressive way to show and hide DOM elements.
9 |
10 | Here's an example of a simple dropdown component using `x-show`.
11 |
12 | ```html
13 |
14 |
15 |
16 |
17 | Dropdown Contents...
18 |
19 |
20 | ```
21 |
22 | When the "Toggle Dropdown" button is clicked, the dropdown will show and hide accordingly.
23 |
24 | > If the "default" state of an `x-show` on page load is "false", you may want to use `x-cloak` on the page to avoid "page flicker" (The effect that happens when the browser renders your content before Alpine is finished initializing and hiding it.) You can learn more about `x-cloak` in its documentation.
25 |
26 |
27 | ## With transitions
28 |
29 | If you want to apply smooth transitions to the `x-show` behavior, you can use it in conjunction with `x-transition`. You can learn more about that directive [here](), but here's a quick example of the same component as above, just with transitions applied.
30 |
31 | ```html
32 |
33 |
34 |
35 |
36 | Dropdown Contents...
37 |
38 |
39 | ```
40 |
--------------------------------------------------------------------------------
/packages/docs/src/en/directives/text.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 6
3 | title: text
4 | ---
5 |
6 | # `x-text`
7 |
8 | `x-text` sets the text content of an element to the result of a given expression.
9 |
10 | Here's a basic example of using `x-text` to display a user's username.
11 |
12 | ```html
13 |
14 | Username:
15 |
16 | ```
17 |
18 |
19 |
20 |
21 | Username:
22 |
23 |
24 |
25 |
26 | Now the `` tag's inner text content will be set to "calebporzio".
27 |
--------------------------------------------------------------------------------
/packages/docs/src/en/essentials.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: Essentials
4 | type: sub-directory
5 | ---
6 |
--------------------------------------------------------------------------------
/packages/docs/src/en/essentials/events.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 4
3 | title: Events
4 | ---
5 |
6 | # Events
7 |
8 | Alpine makes it simple to listen for browser events and react to them.
9 |
10 |
11 | ## Listening for simple events
12 |
13 | By using `x-on`, you can listen for browser events that are dispatched on or within an element.
14 |
15 | Here's a basic example of listening for a click on a button:
16 |
17 | ```html
18 |
19 | ```
20 |
21 | As an alternative, you can use the event shorthand syntax if you prefer: `@`. Here's the same example as before, but using the shorthand syntax (which we'll be using from now on):
22 |
23 | ```html
24 |
25 | ```
26 |
27 | In addition to `click`, you can listen for any browser event by name. For example: `@mouseenter`, `@keyup`, etc... are all valid syntax.
28 |
29 |
30 | ## Listening for specific keys
31 |
32 | Let's say you wanted to listen for the `enter` key to be pressed inside an `` element. Alpine makes this easy by adding the `.enter` like so:
33 |
34 | ```html
35 |
36 | ```
37 |
38 | You can even combine key modifiers to listen for key combinations like pressing `enter` while holding `shift`:
39 |
40 | ```html
41 |
42 | ```
43 |
44 |
45 | ## Preventing default
46 |
47 | When reacting to browser events, it is often necessary to "prevent default" (prevent the default behavior of the browser event).
48 |
49 | For example, if you want to listen for a form submission but prevent the browser from submitting a form request, you can use `.prevent`:
50 |
51 | ```html
52 |
53 | ```
54 |
55 | You can also apply `.stop` to achieve the equivelant of `event.stopPropagation()`.
56 |
57 |
58 | ## Accessing the event object
59 |
60 | Sometimes you may want to access the native browser event object inside your own code. To make this easy, Alpine automatically injects an `$event` magic variable:
61 |
62 | ```html
63 |
64 | ```
65 |
66 |
67 | ## Dispatching custom events
68 |
69 | In addition to listening for browser events, you can dispatch them as well. This is extremely useful for communicating with other Alpine components or event in tools outside of Alpine itself.
70 |
71 | Alpine exposes a magic helper called `$dispatch` for this:
72 |
73 | ```html
74 |
75 |
76 |
77 | ```
78 |
79 | As you can see, when the button is clicked, Alpine will dispatch a browser event called "foo", and our `@foo` listener on the `
` will pick it up and react to it.
80 |
81 |
82 | ## Listening for events on window
83 |
84 | Because of the nature of events in the browser, it is sometimes useful to listen to events on the top-level window object.
85 |
86 | This allows you to communicate across components completely like the following example:
87 |
88 |
89 | ```html
90 |
91 |
92 |
93 |
94 |
...
95 | ```
96 |
97 | In the above example, if we click the button in the first component, Alpine will dispatch the "foo" event. Because of the way events work in the browser, they "bubble" up through parent elements all the way to the top-level "window".
98 |
99 | Now, because in our second component we are listening for "foo" on the window (with `.window`), when the button is clicked, this listener will pick it up and log the "foo was dispatched" message.
100 |
101 | [→ Read more about x-on](/directives/on)
102 |
--------------------------------------------------------------------------------
/packages/docs/src/en/essentials/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: Installation
4 | ---
5 |
6 | # Installation
7 |
8 | There are 2 ways to include Alpine into your project:
9 |
10 | * Including it from a `
26 |
27 | ...
28 |