Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,154 @@
import { findClosest, initTree } from './lifecycle'
import { flushPendingMutations } from './mutation'
import { handleError } from './utils/error'
import { directives } from './directives'
import { interceptClone } from './clone'
// A tree can be suspended while an asynchronous prerequisite loads — a script
// module that registers Alpine.data() providers, for example. While suspended,
// nothing inside the tree initializes: directives don't evaluate, added nodes
// wait, and attribute changes are held for later. When every registered
// promise has settled, the tree initializes in place.
let activeDefers = 0
export function deferInit(el, promise) {
let record = el._x_deferInit
if (! record) {
record = el._x_deferInit = {
pending: 0,
ownsIgnore: ! el._x_ignore,
queuedAttributes: new Map,
}
// Suspension rides on the ignore flag so every existing guard — the
// walker, directive handlers, added-node initialization — already
// respects the boundary. If something else set the flag first, it
// owns the tree and we leave the flag alone when we're done...
if (record.ownsIgnore) el._x_ignore = true
activeDefers++
}
record.pending++
Promise.resolve(promise).catch(error => {
// A rejected prerequisite must not leave the tree suspended forever.
// Surface the error, then initialize anyway. The handler itself may
// throw (custom error handlers can) — rethrow out of band so the
// settle below still runs...
try {
handleError(error, el)
} catch (error) {
setTimeout(() => { throw error }, 0)
}
}).then(() => settle(el, record))
}
function settle(el, record) {
record.pending--
if (record.pending > 0) return
// Deliver any mutations the observer is still holding while the tree is
// still suspended, so pending attribute changes land in the queue below
// instead of firing against a half-ready tree...
flushPendingMutations()
// Handlers that ran during that flush may have registered a new
// prerequisite on this same element — its chain will settle later...
if (record.pending > 0) return
// ...and if the suspension was already torn down (or replaced) by the
// time this chain ran, there's nothing left for it to do...
if (el._x_deferInit !== record) return
delete el._x_deferInit
if (record.ownsIgnore) delete el._x_ignore
activeDefers--
// The element left the DOM while it was suspended. If it re-enters later,
// the mutation observer will initialize it like any other added node...
if (! el.isConnected) return
replayQueuedAttributes(record)
initTree(el)
}
// While a tree is suspended, Alpine's attributes-added handler routes the
// mutation here instead of evaluating directives against a tree that isn't
// ready. Everything queued is replayed when the tree resumes...
export function queueAttributesForDeferredTree(el, attrs) {
if (activeDefers === 0) return false
let root = findClosest(el, i => i._x_deferInit)
if (! root) return false
queueInto(root._x_deferInit, el, attrs.map(({ name }) => name))
return true
}
function queueInto(record, el, names) {
let entry = record.queuedAttributes.get(el)
// The marker pins the entry to this incarnation of the element. If the
// element is destroyed and re-initialized before the replay (it moved out
// of the suspended tree, say), the fresh initialization already applied
// its attributes and a stale entry must not apply them again...
if (! entry || entry.marker !== el._x_marker) {
entry = { marker: el._x_marker, names: new Set }
record.queuedAttributes.set(el, entry)
}
names.forEach(name => entry.names.add(name))
}
function replayQueuedAttributes(record) {
record.queuedAttributes.forEach((entry, el) => {
if (! el.isConnected) return
// Elements without a marker are picked up by the tree walk that
// follows — replaying here would process their attributes twice...
if (! el._x_marker) return
// A different marker means the element was re-initialized since the
// attributes were queued, which already applied them...
if (el._x_marker !== entry.marker) return
// If an enclosing tree is still suspended, this isn't the moment to
// evaluate anything — hand the entry over so it replays when that
// tree resumes...
let suspendedAncestor = findClosest(el, i => i._x_deferInit)
if (suspendedAncestor) {
queueInto(suspendedAncestor._x_deferInit, el, Array.from(entry.names))
return
}
let attrs = Array.from(entry.names)
.filter(name => el.hasAttribute(name))
.map(name => ({ name, value: el.getAttribute(name) }))
if (attrs.length === 0) return
directives(el, attrs).forEach(handle => handle())
})
}
// Morphing evaluates directives on detached clones of live elements, where
// the suspension flag on the live tree can't be seen. Carry it over so a
// suspended tree stays suspended through a morph...
interceptClone((from, to) => {
if (activeDefers === 0) return
if (! from || from.nodeType !== 1 || ! to || to.nodeType !== 1) return
if (findClosest(from, i => i._x_deferInit)) to._x_ignore = true
})