├── packages ├── trap │ ├── .gitignore │ ├── builds │ │ ├── module.js │ │ └── cdn.js │ ├── package.json │ └── src │ │ └── index.js ├── csp │ ├── builds │ │ ├── module.js │ │ └── cdn.js │ ├── package.json │ ├── src │ │ └── index.js │ └── dist │ │ └── module.js ├── alpinejs │ ├── builds │ │ ├── module.js │ │ └── cdn.js │ ├── src │ │ ├── magics │ │ │ ├── $el.js │ │ │ ├── $store.js │ │ │ ├── $nextTick.js │ │ │ ├── index.js │ │ │ ├── $refs.js │ │ │ ├── $dispatch.js │ │ │ └── $watch.js │ │ ├── plugin.js │ │ ├── utils │ │ │ ├── warn.js │ │ │ ├── once.js │ │ │ ├── dispatch.js │ │ │ ├── styles.js │ │ │ ├── walk.js │ │ │ ├── classes.js │ │ │ └── bind.js │ │ ├── datas.js │ │ ├── directives │ │ │ ├── x-effect.js │ │ │ ├── x-cloak.js │ │ │ ├── index.js │ │ │ ├── x-init.js │ │ │ ├── x-ref.js │ │ │ ├── x-text.js │ │ │ ├── x-ignore.js │ │ │ ├── x-on.js │ │ │ ├── x-if.js │ │ │ ├── x-data.js │ │ │ ├── x-bind.js │ │ │ ├── x-show.js │ │ │ └── x-model.js │ │ ├── nextTick.js │ │ ├── magics.js │ │ ├── store.js │ │ ├── scheduler.js │ │ ├── reactivity.js │ │ ├── alpine.js │ │ ├── clone.js │ │ ├── scope.js │ │ ├── lifecycle.js │ │ ├── interceptor.js │ │ ├── index.js │ │ └── evaluator.js │ ├── package.json │ └── node_modules │ │ └── .package-lock.json ├── docs │ ├── src │ │ └── en │ │ │ ├── advanced.md │ │ │ ├── essentials.md │ │ │ ├── magics.md │ │ │ ├── globals.md │ │ │ ├── directives.md │ │ │ ├── magics │ │ │ ├── el.md │ │ │ ├── nextTick.md │ │ │ ├── refs.md │ │ │ ├── watch.md │ │ │ ├── store.md │ │ │ └── dispatch.md │ │ │ ├── directives │ │ │ ├── ignore.md │ │ │ ├── ref.md │ │ │ ├── text.md │ │ │ ├── if.md │ │ │ ├── effect.md │ │ │ ├── show.md │ │ │ ├── cloak.md │ │ │ ├── init.md │ │ │ └── for.md │ │ │ ├── advanced │ │ │ ├── async.md │ │ │ ├── csp.md │ │ │ └── reactivity.md │ │ │ ├── essentials │ │ │ ├── installation.md │ │ │ ├── lifecycle.md │ │ │ └── events.md │ │ │ └── globals │ │ │ ├── alpine-store.md │ │ │ └── alpine-data.md │ └── package.json ├── history │ ├── builds │ │ ├── module.js │ │ └── cdn.js │ ├── package.json │ ├── dist │ │ ├── cdn.min.js │ │ ├── module.esm.js │ │ ├── cdn.js │ │ └── module.cjs.js │ └── src │ │ ├── url.js │ │ └── index.js ├── persist │ ├── builds │ │ ├── module.js │ │ └── cdn.js │ ├── dist │ │ ├── cdn.min.js │ │ ├── module.esm.js │ │ ├── cdn.js │ │ └── module.cjs.js │ ├── package.json │ └── src │ │ └── index.js ├── intersect │ ├── builds │ │ ├── module.js │ │ └── cdn.js │ ├── package.json │ ├── dist │ │ ├── cdn.min.js │ │ ├── module.esm.js │ │ ├── cdn.js │ │ └── module.cjs.js │ ├── node_modules │ │ └── .package-lock.json │ └── src │ │ └── index.js └── morph │ ├── builds │ ├── module.js │ └── cdn.js │ ├── package.json │ ├── src │ └── index.js │ └── dist │ └── cdn.min.js ├── tests ├── cypress │ ├── .gitignore │ ├── fixtures │ │ └── example.json │ ├── integration │ │ ├── directives │ │ │ ├── x-cloak.spec.js │ │ │ ├── x-if.spec.js │ │ │ ├── x-ignore.spec.js │ │ │ ├── x-bind:style.spec.js │ │ │ ├── x-text.spec.js │ │ │ ├── x-init.spec.js │ │ │ ├── x-transition.spec.js │ │ │ ├── x-bind:class.spec.js │ │ │ ├── x-model.spec.js │ │ │ └── x-data.spec.js │ │ ├── magics │ │ │ ├── $el.spec.js │ │ │ ├── $dispatch.spec.js │ │ │ ├── $refs.spec.js │ │ │ ├── $nextTick.spec.js │ │ │ └── $watch.spec.js │ │ ├── custom-prefix.spec.js │ │ ├── plugins │ │ │ ├── persist.spec.js │ │ │ ├── csp-compatibility.spec.js │ │ │ ├── intersect.spec.js │ │ │ ├── trap.spec.js │ │ │ ├── morph.spec.js │ │ │ └── history.spec.js │ │ ├── custom-magics.spec.js │ │ ├── custom-directives.spec.js │ │ ├── mutation.spec.js │ │ ├── store.spec.js │ │ ├── custom-data.spec.js │ │ └── clone.spec.js │ ├── support │ │ ├── index.js │ │ └── commands.js │ ├── plugins │ │ └── index.js │ ├── spec-csp.html │ ├── spec.html │ ├── utils.js │ └── manual-transition-test.html └── jest │ └── morph │ ├── createElement.js │ ├── external.spec.js │ ├── alpine-scope.spec.js │ └── internal.spec.js ├── .gitignore ├── jest.config.js ├── README.md ├── editorconfig ├── cypress.json ├── benchmarks ├── memory.html └── mutation_observer.html ├── package.json ├── LICENSE.md └── scripts └── build.js /packages/trap/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/cypress/.gitignore: -------------------------------------------------------------------------------- 1 | /videos 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | scratch.md 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /packages/csp/builds/module.js: -------------------------------------------------------------------------------- 1 | import Alpine from './../src/index' 2 | 3 | export default Alpine 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | rootDir: './tests/jest', 3 | } 4 | 5 | module.exports = config 6 | -------------------------------------------------------------------------------- /packages/alpinejs/builds/module.js: -------------------------------------------------------------------------------- 1 | import Alpine from './../src/index' 2 | 3 | export default Alpine 4 | -------------------------------------------------------------------------------- /packages/alpinejs/src/magics/$el.js: -------------------------------------------------------------------------------- 1 | import { magic } from "../magics"; 2 | 3 | magic('el', el => el) 4 | -------------------------------------------------------------------------------- /packages/docs/src/en/advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 8 3 | title: Advanced 4 | type: sub-directory 5 | --- 6 | -------------------------------------------------------------------------------- /packages/history/builds/module.js: -------------------------------------------------------------------------------- 1 | import history from '../src/index.js' 2 | 3 | export default history 4 | -------------------------------------------------------------------------------- /packages/persist/builds/module.js: -------------------------------------------------------------------------------- 1 | import persist from '../src/index.js' 2 | 3 | export default persist 4 | -------------------------------------------------------------------------------- /packages/docs/src/en/essentials.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | title: Essentials 4 | type: sub-directory 5 | --- 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### IMPORTANT: 2 | This repo has moved to the main Alpine repo here: https://github.com/alpinejs/alpine 3 | -------------------------------------------------------------------------------- /packages/intersect/builds/module.js: -------------------------------------------------------------------------------- 1 | import intersect from './../src/index.js' 2 | 3 | export default intersect 4 | -------------------------------------------------------------------------------- /packages/trap/builds/module.js: -------------------------------------------------------------------------------- 1 | import registerTrapDirective from '../src/index.js' 2 | 3 | export default registerTrapDirective 4 | -------------------------------------------------------------------------------- /packages/docs/src/en/magics.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 5 3 | title: Magics 4 | prefix: $ 5 | font-type: mono 6 | type: sub-directory 7 | --- 8 | -------------------------------------------------------------------------------- /packages/alpinejs/src/plugin.js: -------------------------------------------------------------------------------- 1 | import Alpine from './alpine' 2 | 3 | export function plugin(callback) { 4 | callback(Alpine) 5 | } 6 | -------------------------------------------------------------------------------- /packages/docs/src/en/globals.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 6 3 | title: Globals 4 | font-type: mono 5 | prefix: Alpine. 6 | type: sub-directory 7 | --- 8 | -------------------------------------------------------------------------------- /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/alpinejs/src/magics/$store.js: -------------------------------------------------------------------------------- 1 | import { getStores } from '../store' 2 | import { magic } from '../magics' 3 | 4 | magic('store', getStores) 5 | -------------------------------------------------------------------------------- /packages/alpinejs/src/utils/warn.js: -------------------------------------------------------------------------------- 1 | 2 | export function warn(message, ...args) { 3 | console.warn(`Alpine Warning: ${message}`, ...args) 4 | } 5 | -------------------------------------------------------------------------------- /packages/morph/builds/module.js: -------------------------------------------------------------------------------- 1 | import morphPlugin, { morph } from '../src/index.js' 2 | 3 | export default morphPlugin 4 | 5 | export { morph } 6 | -------------------------------------------------------------------------------- /packages/csp/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/cdn.js: -------------------------------------------------------------------------------- 1 | import Alpine from './../src/index' 2 | 3 | window.Alpine = Alpine 4 | 5 | queueMicrotask(() => { 6 | Alpine.start() 7 | }) 8 | -------------------------------------------------------------------------------- /packages/alpinejs/src/magics/$nextTick.js: -------------------------------------------------------------------------------- 1 | import { nextTick } from '../nextTick' 2 | import { magic } from '../magics' 3 | 4 | magic('nextTick', () => nextTick) 5 | -------------------------------------------------------------------------------- /packages/alpinejs/src/magics/index.js: -------------------------------------------------------------------------------- 1 | import './$nextTick' 2 | import './$dispatch' 3 | import './$watch' 4 | import './$store' 5 | import './$refs' 6 | import './$el' 7 | -------------------------------------------------------------------------------- /packages/morph/builds/cdn.js: -------------------------------------------------------------------------------- 1 | import morph from '../src/index.js' 2 | 3 | document.addEventListener('alpine:initializing', () => { 4 | window.Alpine.plugin(morph) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/trap/builds/cdn.js: -------------------------------------------------------------------------------- 1 | import trap from '../src/index.js' 2 | 3 | document.addEventListener('alpine:initializing', () => { 4 | window.Alpine.plugin(trap) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/alpinejs/src/magics/$refs.js: -------------------------------------------------------------------------------- 1 | import { closestRoot } from '../lifecycle' 2 | import { magic } from '../magics' 3 | 4 | magic('refs', el => closestRoot(el)._x_refs || {}) 5 | -------------------------------------------------------------------------------- /packages/history/builds/cdn.js: -------------------------------------------------------------------------------- 1 | import history from '../src/index.js' 2 | 3 | document.addEventListener('alpine:initializing', () => { 4 | window.Alpine.plugin(history) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/persist/builds/cdn.js: -------------------------------------------------------------------------------- 1 | import persist from '../src/index.js' 2 | 3 | document.addEventListener('alpine:initializing', () => { 4 | window.Alpine.plugin(persist) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/alpinejs/src/magics/$dispatch.js: -------------------------------------------------------------------------------- 1 | import { dispatch } from '../utils/dispatch' 2 | import { magic } from '../magics' 3 | 4 | magic('dispatch', el => dispatch.bind(dispatch, el)) 5 | -------------------------------------------------------------------------------- /packages/intersect/builds/cdn.js: -------------------------------------------------------------------------------- 1 | import intersect from '../src/index.js' 2 | 3 | document.addEventListener('alpine:initializing', () => { 4 | window.Alpine.plugin(intersect) 5 | }) 6 | -------------------------------------------------------------------------------- /tests/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alpinejs/docs", 3 | "version": "3.0.0-alpha.0", 4 | "description": "The documentation for Alpine", 5 | "author": "Caleb Porzio", 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/persist/dist/cdn.min.js: -------------------------------------------------------------------------------- 1 | (()=>{function n(r){r.magic("persist",(o,{interceptor:l})=>l((s,t)=>({init(e,i){localStorage.getItem(t)?i(localStorage.getItem(t)):i(e)},set(e,i){localStorage.setItem(t,e),i(e)}})))}document.addEventListener("alpine:initializing",()=>{window.Alpine.plugin(n)});})(); 2 | -------------------------------------------------------------------------------- /packages/persist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alpinejs/persist", 3 | "version": "3.0.0-alpha.0", 4 | "description": "Persist Alpine data to localStorage or similar client-side stores", 5 | "author": "Caleb Porzio", 6 | "license": "MIT", 7 | "main": "dist/module.cjs.js", 8 | "module": "dist/module.esm.js" 9 | } 10 | -------------------------------------------------------------------------------- /tests/cypress/integration/directives/x-cloak.spec.js: -------------------------------------------------------------------------------- 1 | import { html, notHaveAttribute, test } from '../../utils' 2 | 3 | test('x-cloak is removed', 4 | html` 5 |
6 | 7 |
8 | `, 9 | ({ get }) => get('span').should(notHaveAttribute('x-cloak')) 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 | -------------------------------------------------------------------------------- /tests/jest/morph/createElement.js: -------------------------------------------------------------------------------- 1 | 2 | function createElement(htmlOrTemplate) { 3 | if (typeof htmlOrTemplate === 'string') { 4 | return document.createRange().createContextualFragment(htmlOrTemplate).firstElementChild 5 | } 6 | 7 | return htmlOrTemplate.content.firstElementChild.cloneNode(true) 8 | } 9 | 10 | module.exports = createElement 11 | -------------------------------------------------------------------------------- /packages/trap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alpinejs/trap", 3 | "version": "3.0.0-alpha.0", 4 | "description": "Declaratively trap focus", 5 | "author": "Caleb Porzio", 6 | "license": "MIT", 7 | "main": "dist/module.cjs.js", 8 | "module": "dist/module.esm.js", 9 | "dependencies": { 10 | "wicg-inert": "^3.1.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/alpinejs/src/utils/once.js: -------------------------------------------------------------------------------- 1 | 2 | export function once(callback, fallback = () => {}) { 3 | let called = false 4 | 5 | return function () { 6 | if (! called) { 7 | called = true 8 | 9 | callback.apply(this, arguments) 10 | } else { 11 | fallback.apply(this, arguments) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/intersect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alpinejs/intersect", 3 | "version": "3.0.0-alpha.0", 4 | "description": "Trigger JavaScript when an element enters or leaves the viewport", 5 | "author": "Caleb Porzio", 6 | "license": "MIT", 7 | "main": "dist/module.cjs.js", 8 | "module": "dist/module.esm.js", 9 | "dependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /packages/alpinejs/src/utils/dispatch.js: -------------------------------------------------------------------------------- 1 | 2 | export function dispatch(el, name, detail = {}) { 3 | el.dispatchEvent( 4 | new CustomEvent(name, { 5 | detail, 6 | bubbles: true, 7 | // Allows events to pass the shadow DOM barrier. 8 | composed: true, 9 | cancelable: true, 10 | }) 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/csp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alpinejs/csp", 3 | "version": "3.0.0-alpha.0", 4 | "description": "A CSP compatible build of Alpine", 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/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/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/history/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alpinejs/history", 3 | "version": "3.0.0-alpha.0", 4 | "description": "Sync Alpine data with the browser's query string", 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/morph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@alpinejs/morph", 3 | "version": "3.0.0-alpha.0", 4 | "description": "Diff and patch a block of HTML on a page with an HTML template", 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/cypress/integration/magics/$el.spec.js: -------------------------------------------------------------------------------- 1 | import { haveText, html, test } from '../../utils' 2 | 3 | test('$el returns the current element', 4 | html` 5 |
6 | 7 |
8 | `, 9 | ({ get }) => { 10 | get('button').should(haveText('click me')) 11 | get('button').click() 12 | get('button').should(haveText('foo')) 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /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/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/docs/src/en/magics/el.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | prefix: $ 4 | title: el 5 | --- 6 | 7 | # `$el` 8 | 9 | `$el` is a magic property that can be used to retrieve the current DOM node. 10 | 11 | ```html 12 | 13 | ``` 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /tests/cypress/integration/custom-prefix.spec.js: -------------------------------------------------------------------------------- 1 | import { haveText, html, test } from '../utils' 2 | 3 | test('can set a custom x- prefix', 4 | html` 5 | 10 | 11 |
12 | 13 |
14 | `, 15 | ({ get }) => get('span').should(haveText('bar')) 16 | ) 17 | -------------------------------------------------------------------------------- /packages/alpinejs/src/nextTick.js: -------------------------------------------------------------------------------- 1 | 2 | let tickStack = [] 3 | 4 | let isHolding = false 5 | 6 | export function nextTick(callback) { 7 | tickStack.push(callback) 8 | 9 | queueMicrotask(() => { 10 | isHolding || setTimeout(() => { 11 | releaseNextTicks() 12 | }) 13 | }) 14 | } 15 | 16 | export function releaseNextTicks() { 17 | isHolding = false 18 | 19 | while (tickStack.length) tickStack.shift()() 20 | } 21 | 22 | export function holdNextTicks() { 23 | isHolding = true 24 | } 25 | -------------------------------------------------------------------------------- /benchmarks/memory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 8 | 9 | 10 |
11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /packages/morph/src/index.js: -------------------------------------------------------------------------------- 1 | import { morph } from './morph' 2 | 3 | export default function (Alpine) { 4 | Alpine.directive('morph', (el, { expression }, { effect, evaluateLater }) => { 5 | let evaluate = evaluateLater(expression) 6 | 7 | effect(() => { 8 | evaluate(value => { 9 | let child = el.firstElementChild || el.firstChild || el.appendChild(document.createTextNode('')) 10 | 11 | morph(child, value) 12 | }) 13 | }) 14 | }) 15 | } 16 | 17 | export { morph } 18 | -------------------------------------------------------------------------------- /packages/alpinejs/src/magics.js: -------------------------------------------------------------------------------- 1 | import Alpine from './alpine' 2 | import { interceptor } from './interceptor' 3 | 4 | let magics = {} 5 | 6 | export function magic(name, callback) { 7 | magics[name] = callback 8 | } 9 | 10 | export function injectMagics(obj, el) { 11 | Object.entries(magics).forEach(([name, callback]) => { 12 | Object.defineProperty(obj, `$${name}`, { 13 | get() { return callback(el, { Alpine, interceptor }) }, 14 | 15 | enumerable: true, 16 | }) 17 | }) 18 | 19 | return obj 20 | } 21 | -------------------------------------------------------------------------------- /tests/cypress/integration/plugins/persist.spec.js: -------------------------------------------------------------------------------- 1 | import { haveText, html, test } from '../../utils' 2 | 3 | test('can perist data', 4 | [html` 5 |
6 | 7 | 8 |
9 | `], 10 | ({ get }, reload) => { 11 | get('span').should(haveText('1')) 12 | get('button').click() 13 | get('span').should(haveText('2')) 14 | reload() 15 | get('span').should(haveText('2')) 16 | }, 17 | ) 18 | -------------------------------------------------------------------------------- /packages/alpinejs/src/store.js: -------------------------------------------------------------------------------- 1 | import { reactive } from "./reactivity" 2 | 3 | let stores = {} 4 | let isReactive = false 5 | 6 | export function store(name, value) { 7 | if (! isReactive) { stores = reactive(stores); isReactive = true; } 8 | 9 | if (value === undefined) { 10 | return stores[name] 11 | } 12 | 13 | if (typeof value === 'object' && value !== null && value.hasOwnProperty('init') && typeof value.init === 'function') { 14 | value.init() 15 | } 16 | 17 | stores[name] = value 18 | } 19 | 20 | export function getStores() { return stores } 21 | -------------------------------------------------------------------------------- /tests/cypress/integration/custom-magics.spec.js: -------------------------------------------------------------------------------- 1 | import { haveText, html, test } from '../utils' 2 | 3 | test('can register custom magic properties', 4 | html` 5 | 12 | 13 |
14 | 15 |
16 | `, 17 | ({ get }) => get('span').should(haveText('baz')) 18 | ) 19 | -------------------------------------------------------------------------------- /tests/cypress/integration/magics/$dispatch.spec.js: -------------------------------------------------------------------------------- 1 | import { haveText, html, test } from '../../utils' 2 | 3 | test('$dispatch dispatches events properly', 4 | html` 5 |
6 | 7 | 8 | 9 |
10 | `, 11 | ({ get }) => { 12 | get('span').should(haveText('bar')) 13 | get('button').click() 14 | get('span').should(haveText('baz')) 15 | } 16 | ) 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/cypress/integration/directives/x-if.spec.js: -------------------------------------------------------------------------------- 1 | import { beVisible, html, notBeVisible, notHaveAttribute, test } from '../../utils' 2 | 3 | test('x-if', 4 | html` 5 |
6 | 7 | 8 | 11 |
12 | `, 13 | ({ get }) => { 14 | get('h1').should(notBeVisible()) 15 | get('button').click() 16 | get('h1').should(beVisible()) 17 | get('button').click() 18 | get('h1').should(notBeVisible()) 19 | } 20 | ) 21 | -------------------------------------------------------------------------------- /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/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/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/alpinejs/src/magics/$watch.js: -------------------------------------------------------------------------------- 1 | import { evaluateLater } from '../evaluator' 2 | import { effect } from '../reactivity' 3 | import { magic } from '../magics' 4 | 5 | magic('watch', el => (key, callback) => { 6 | let evaluate = evaluateLater(el, key) 7 | 8 | let firstTime = true 9 | 10 | let oldValue 11 | 12 | effect(() => evaluate(value => { 13 | // This is a hack to force deep reactivity for things like "items.push()". 14 | let div = document.createElement('div') 15 | div.dataset.throwAway = value 16 | 17 | if (! firstTime) callback(value, oldValue) 18 | 19 | oldValue = value 20 | 21 | firstTime = false 22 | })) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/cypress/integration/plugins/csp-compatibility.spec.js: -------------------------------------------------------------------------------- 1 | import { haveText, html, test } from '../../utils' 2 | 3 | test.csp('Can use components and basic expressions with CSP-compatible build', 4 | [html` 5 |
6 | 7 | 8 | 9 |
10 | `, 11 | ` 12 | Alpine.data('test', () => ({ 13 | foo: 'bar', 14 | change() { this.foo = 'baz' }, 15 | })) 16 | `], 17 | ({ get }) => { 18 | get('span').should(haveText('bar')) 19 | get('button').click() 20 | get('span').should(haveText('baz')) 21 | } 22 | ) 23 | -------------------------------------------------------------------------------- /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/persist/src/index.js: -------------------------------------------------------------------------------- 1 | 2 | export default function (Alpine) { 3 | Alpine.magic('persist', (el, { interceptor }) => { 4 | return interceptor((key, path) => { 5 | return { 6 | init(initialValue, setter) { 7 | if (localStorage.getItem(path)) { 8 | setter(localStorage.getItem(path)) 9 | } else { 10 | setter(initialValue) 11 | } 12 | }, 13 | set(value, setter) { 14 | localStorage.setItem(path, value) 15 | 16 | setter(value) 17 | }, 18 | } 19 | }) 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /packages/alpinejs/src/scheduler.js: -------------------------------------------------------------------------------- 1 | 2 | let flushPending = false 3 | let flushing = false 4 | let queue = [] 5 | 6 | export function scheduler (callback) { queueJob(callback) } 7 | 8 | function queueJob(job) { 9 | if (! queue.includes(job)) queue.push(job) 10 | 11 | queueFlush() 12 | } 13 | 14 | function queueFlush() { 15 | if (! flushing && ! flushPending) { 16 | flushPending = true 17 | 18 | queueMicrotask(flushJobs) 19 | } 20 | } 21 | 22 | export function flushJobs() { 23 | flushPending = false 24 | flushing = true 25 | 26 | for (let i = 0; i < queue.length; i++) { 27 | queue[i]() 28 | } 29 | 30 | queue.length = 0 31 | 32 | flushing = false 33 | } 34 | -------------------------------------------------------------------------------- /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 `