Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/alpinejs/alpine into featur…
Browse files Browse the repository at this point in the history
…e/ui/combobox
  • Loading branch information
SimoTod committed May 13, 2023
2 parents 53dc53a + b5920c2 commit 92254b3
Show file tree
Hide file tree
Showing 35 changed files with 2,290 additions and 5,904 deletions.
Binary file added .DS_Store
Binary file not shown.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Stay here for contribution-related information.

> Looking for V2 docs? [here they are](https://github.com/alpinejs/alpine/tree/v2.8.2)
<p align="center"><a href="https://alpinejs.dev/patterns"><img src="/hero.jpg" alt="Alpine Compoenent Patterns"></a></p>
<p align="center"><a href="https://alpinejs.dev/patterns"><img src="/hero.jpg" alt="Alpine Component Patterns"></a></p>

## Contribution Guide:

Expand Down
7,748 changes: 1,925 additions & 5,823 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,5 @@
"jest": "jest test",
"update-docs": "node ./scripts/update-docs.js",
"release": "node ./scripts/release.js"
},
"dependencies": {
"nprogress": "^0.2.0"
}
}
2 changes: 1 addition & 1 deletion packages/alpinejs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alpinejs",
"version": "3.12.0",
"version": "3.12.1",
"description": "The rugged, minimal JavaScript framework",
"homepage": "https://alpinejs.dev",
"repository": {
Expand Down
18 changes: 14 additions & 4 deletions packages/alpinejs/src/directives/x-for.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { addScopeToNode, refreshScope } from '../scope'
import { addScopeToNode } from '../scope'
import { evaluateLater } from '../evaluator'
import { directive } from '../directives'
import { reactive } from '../reactivity'
Expand Down Expand Up @@ -163,6 +163,8 @@ function loop(el, iteratorNames, evaluateItems, evaluateKey) {
let marker = document.createElement('div')

mutateDom(() => {
if (! elForSpot) warn(`x-for ":key" is undefined or invalid`, templateEl)

elForSpot.after(marker)
elInSpot.after(elForSpot)
elForSpot._x_currentIfEl && elForSpot.after(elForSpot._x_currentIfEl)
Expand All @@ -171,7 +173,7 @@ function loop(el, iteratorNames, evaluateItems, evaluateKey) {
marker.remove()
})

refreshScope(elForSpot, scopes[keys.indexOf(keyForSpot)])
elForSpot._x_refreshXForScope(scopes[keys.indexOf(keyForSpot)])
}

// We can now create and add new elements.
Expand All @@ -188,7 +190,15 @@ function loop(el, iteratorNames, evaluateItems, evaluateKey) {

let clone = document.importNode(templateEl.content, true).firstElementChild

addScopeToNode(clone, reactive(scope), templateEl)
let reactiveScope = reactive(scope)

addScopeToNode(clone, reactiveScope, templateEl)

clone._x_refreshXForScope = (newScope) => {
Object.entries(newScope).forEach(([key, value]) => {
reactiveScope[key] = value
})
}

mutateDom(() => {
lastEl.after(clone)
Expand All @@ -207,7 +217,7 @@ function loop(el, iteratorNames, evaluateItems, evaluateKey) {
// data it depends on in case the data has changed in an
// "unobservable" way.
for (let i = 0; i < sames.length; i++) {
refreshScope(lookup[sames[i]], scopes[keys.indexOf(sames[i])])
lookup[sames[i]]._x_refreshXForScope(scopes[keys.indexOf(sames[i])])
}

// Now we'll log the keys (and the order they're in) for comparing
Expand Down
15 changes: 7 additions & 8 deletions packages/alpinejs/src/directives/x-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
})
}
}

if (modifiers.includes('fill') && el.hasAttribute('value') && (getValue() === null || getValue() === '')) {
setValue(el.value)
}

if (typeof expression === 'string' && el.type === 'radio') {
// Radio buttons only work properly when they share a name attribute.
Expand All @@ -73,7 +69,10 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
let removeListener = isCloning ? () => {} : on(el, event, modifiers, (e) => {
setValue(getInputValue(el, modifiers, e, getValue()))
})


if (modifiers.includes('fill') && [null, ''].includes(getValue())) {
el.dispatchEvent(new Event(event, {}));
}
// Register the listener removal callback on the element, so that
// in addition to the cleanup function, x-modelable may call it.
// Also, make this a keyed object if we decide to reintroduce
Expand Down Expand Up @@ -134,9 +133,9 @@ function getInputValue(el, modifiers, event, currentValue) {
// Check for event.detail due to an issue where IE11 handles other events as a CustomEvent.
// Safari autofill triggers event as CustomEvent and assigns value to target
// so we return event.target.value instead of event.detail
if (event instanceof CustomEvent && event.detail !== undefined) {
return typeof event.detail != 'undefined' ? event.detail : event.target.value
} else if (el.type === 'checkbox') {
if (event instanceof CustomEvent && event.detail !== undefined)
return event.detail ?? event.target.value
else if (el.type === 'checkbox') {
// If the data we are binding to is an array, toggle its value inside the array.
if (Array.isArray(currentValue)) {
let newValue = modifiers.includes('number') ? safeParseNumber(event.target.value) : event.target.value
Expand Down
12 changes: 6 additions & 6 deletions packages/alpinejs/src/directives/x-transition.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { once } from '../utils/once'

directive('transition', (el, { value, modifiers, expression }, { evaluate }) => {
if (typeof expression === 'function') expression = evaluate(expression)

if (! expression) {
if (expression === false) return
if (!expression || typeof expression === 'boolean') {
registerTransitionsFromHelper(el, modifiers, value)
} else {
registerTransitionsFromClassString(el, expression, value)
Expand Down Expand Up @@ -50,7 +50,7 @@ function registerTransitionsFromHelper(el, modifiers, stage) {
let wantsScale = wantsAll || modifiers.includes('scale')
let opacityValue = wantsOpacity ? 0 : 1
let scaleValue = wantsScale ? modifierValue(modifiers, 'scale', 95) / 100 : 1
let delay = modifierValue(modifiers, 'delay', 0)
let delay = modifierValue(modifiers, 'delay', 0) / 1000
let origin = modifierValue(modifiers, 'origin', 'center')
let property = 'opacity, transform'
let durationIn = modifierValue(modifiers, 'duration', 150) / 1000
Expand All @@ -60,7 +60,7 @@ function registerTransitionsFromHelper(el, modifiers, stage) {
if (transitioningIn) {
el._x_transition.enter.during = {
transformOrigin: origin,
transitionDelay: delay,
transitionDelay: `${delay}s`,
transitionProperty: property,
transitionDuration: `${durationIn}s`,
transitionTimingFunction: easing,
Expand All @@ -80,7 +80,7 @@ function registerTransitionsFromHelper(el, modifiers, stage) {
if (transitioningOut) {
el._x_transition.leave.during = {
transformOrigin: origin,
transitionDelay: delay,
transitionDelay: `${delay}s`,
transitionProperty: property,
transitionDuration: `${durationOut}s`,
transitionTimingFunction: easing,
Expand Down Expand Up @@ -318,7 +318,7 @@ export function modifierValue(modifiers, key, fallback) {
if (isNaN(rawValue)) return fallback
}

if (key === 'duration') {
if (key === 'duration' || key === 'delay') {
// Support x-transition.duration.500ms && duration.500
let match = rawValue.match(/([0-9]+)ms/)
if (match) return match[1]
Expand Down
6 changes: 6 additions & 0 deletions packages/alpinejs/src/lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { dispatch } from './utils/dispatch'
import { walk } from "./utils/walk"
import { warn } from './utils/warn'

let started = false

export function start() {
if (started) warn('Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems.')

started = true

if (! document.body) warn('Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine\'s `<script>` tag?')

dispatch(document, 'alpine:init')
Expand Down
19 changes: 13 additions & 6 deletions packages/alpinejs/src/magics.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,24 @@ export function magic(name, callback) {

export function injectMagics(obj, el) {
Object.entries(magics).forEach(([name, callback]) => {
Object.defineProperty(obj, `$${name}`, {
get() {
let memoizedUtilities = null;
function getUtilities() {
if (memoizedUtilities) {
return memoizedUtilities;
} else {
let [utilities, cleanup] = getElementBoundUtilities(el)

utilities = {interceptor, ...utilities}
memoizedUtilities = {interceptor, ...utilities}

onElRemoved(el, cleanup)

return callback(el, utilities)
return memoizedUtilities;
}
}

Object.defineProperty(obj, `$${name}`, {
get() {
return callback(el, getUtilities());
},

enumerable: false,
})
})
Expand Down
6 changes: 4 additions & 2 deletions packages/alpinejs/src/plugin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Alpine from './alpine'
import Alpine from "./alpine";

export function plugin(callback) {
callback(Alpine)
let callbacks = Array.isArray(callback) ? callback : [callback]

callbacks.forEach(i => i(Alpine))
}
12 changes: 2 additions & 10 deletions packages/alpinejs/src/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ export function hasScope(node) {
return !! node._x_dataStack
}

export function refreshScope(element, scope) {
let existingScope = element._x_dataStack[0]

Object.entries(scope).forEach(([key, value]) => {
existingScope[key] = value
})
}

export function closestDataStack(node) {
if (node._x_dataStack) return node._x_dataStack

Expand Down Expand Up @@ -60,7 +52,7 @@ export function mergeProxies(objects) {
if ((descriptor.get && descriptor.get._x_alreadyBound) || (descriptor.set && descriptor.set._x_alreadyBound)) {
return true
}

// Properly bind getters and setters to this wrapper Proxy.
if ((descriptor.get || descriptor.set) && descriptor.enumerable) {
// Only bind user-defined getters, not our magic properties.
Expand All @@ -81,7 +73,7 @@ export function mergeProxies(objects) {
})
}

return true
return true
}

return false
Expand Down
19 changes: 19 additions & 0 deletions packages/alpinejs/src/utils/bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ export default function bind(el, name, value, modifiers = []) {
case 'class':
bindClasses(el, value)
break;

// 'selected' and 'checked' are special attributes that aren't necessarily
// synced with their corresponding properties when updated, so both the
// attribute and property need to be updated when bound.
case 'selected':
case 'checked':
bindAttributeAndProperty(el, name, value)
break;

default:
bindAttribute(el, name, value)
Expand Down Expand Up @@ -79,6 +87,11 @@ function bindStyles(el, value) {
el._x_undoAddedStyles = setStyles(el, value)
}

function bindAttributeAndProperty(el, name, value) {
bindAttribute(el, name, value)
setPropertyIfChanged(el, name, value)
}

function bindAttribute(el, name, value) {
if ([null, undefined, false].includes(value) && attributeShouldntBePreservedIfFalsy(name)) {
el.removeAttribute(name)
Expand All @@ -95,6 +108,12 @@ function setIfChanged(el, attrName, value) {
}
}

function setPropertyIfChanged(el, propName, value) {
if (el[propName] !== value) {
el[propName] = value
}
}

function updateSelect(el, value) {
const arrayWrappedValue = [].concat(value).map(value => { return value + '' })

Expand Down
31 changes: 17 additions & 14 deletions packages/alpinejs/src/utils/on.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ export default function on (el, event, modifiers, callback) {
if (modifiers.includes('capture')) options.capture = true
if (modifiers.includes('window')) listenerTarget = window
if (modifiers.includes('document')) listenerTarget = document

// By wrapping the handler with debounce & throttle first, we ensure that the wrapping logic itself is not
// throttled/debounced, only the user's callback is. This way, if the user expects
// `e.preventDefault()` to happen, it'll still happen even if their callback gets throttled.
if (modifiers.includes('debounce')) {
let nextModifier = modifiers[modifiers.indexOf('debounce')+1] || 'invalid-wait'
let wait = isNumeric(nextModifier.split('ms')[0]) ? Number(nextModifier.split('ms')[0]) : 250

handler = debounce(handler, wait)
}
if (modifiers.includes('throttle')) {
let nextModifier = modifiers[modifiers.indexOf('throttle')+1] || 'invalid-wait'
let wait = isNumeric(nextModifier.split('ms')[0]) ? Number(nextModifier.split('ms')[0]) : 250

handler = throttle(handler, wait)
}

if (modifiers.includes('prevent')) handler = wrapHandler(handler, (next, e) => { e.preventDefault(); next(e) })
if (modifiers.includes('stop')) handler = wrapHandler(handler, (next, e) => { e.stopPropagation(); next(e) })
if (modifiers.includes('self')) handler = wrapHandler(handler, (next, e) => { e.target === el && next(e) })
Expand Down Expand Up @@ -59,20 +76,6 @@ export default function on (el, event, modifiers, callback) {
next(e)
})

if (modifiers.includes('debounce')) {
let nextModifier = modifiers[modifiers.indexOf('debounce')+1] || 'invalid-wait'
let wait = isNumeric(nextModifier.split('ms')[0]) ? Number(nextModifier.split('ms')[0]) : 250

handler = debounce(handler, wait)
}

if (modifiers.includes('throttle')) {
let nextModifier = modifiers[modifiers.indexOf('throttle')+1] || 'invalid-wait'
let wait = isNumeric(nextModifier.split('ms')[0]) ? Number(nextModifier.split('ms')[0]) : 250

handler = throttle(handler, wait)
}

listenerTarget.addEventListener(event, handler, options)

return () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/collapse/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alpinejs/collapse",
"version": "3.12.0",
"version": "3.12.1",
"description": "Collapse and expand elements with robust animations",
"homepage": "https://alpinejs.dev/plugins/collapse",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alpinejs/docs",
"version": "3.12.0-revision.1",
"version": "3.12.1-revision.1",
"description": "The documentation for Alpine",
"author": "Caleb Porzio",
"license": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/src/en/essentials/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This is by far the simplest way to get started with Alpine. Include the followin
Notice the `@3.x.x` in the provided CDN link. This will pull the latest version of Alpine version 3. For stability in production, it's recommended that you hardcode the latest version in the CDN link.

```alpine
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.0/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.1/dist/cdn.min.js"></script>
```

That's it! Alpine is now available for use inside your page.
Expand Down
6 changes: 0 additions & 6 deletions packages/docs/src/en/ui.md

This file was deleted.

2 changes: 1 addition & 1 deletion packages/focus/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alpinejs/focus",
"version": "3.12.0",
"version": "3.12.1",
"description": "Manage focus within a page",
"homepage": "https://alpinejs.dev/plugins/focus",
"repository": {
Expand Down
11 changes: 8 additions & 3 deletions packages/focus/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,17 @@ export default function (Alpine) {

let oldValue = false

let trap = createFocusTrap(el, {
let options = {
escapeDeactivates: false,
allowOutsideClick: true,
fallbackFocus: () => el,
initialFocus: el.querySelector('[autofocus]')
})
}

let autofocusEl = el.querySelector('[autofocus]')

if (autofocusEl) options.initialFocus = autofocusEl

let trap = createFocusTrap(el, options)

let undoInert = () => {}
let undoDisableScrolling = () => {}
Expand Down
Loading

0 comments on commit 92254b3

Please sign in to comment.