diff --git a/README.md b/README.md
index 0c039d3..efee773 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@
**`Ramstack.AlpineGear.js`** provides useful and convenient plugins for [Alpine.js](https://alpinejs.dev/).
+The plugins require Alpine.js 3.15.12 or later.
+
## Included Plugins
**[@ramstack/alpinegear-bound](https://www.npmjs.com/package/@ramstack/alpinegear-bound)** ([README](https://github.com/rameel/ramstack.alpinegear.js/tree/main/src/plugins/bound))
diff --git a/package.json b/package.json
index 834c730..36fca59 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"test:vitest": "vitest run"
},
"peerDependencies": {
- "alpinejs": "^3.13.0"
+ "alpinejs": "^3.15.12"
},
"devDependencies": {
"@conventional-changelog/git-client": "^3.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2927607..a43a7cc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,7 +9,7 @@ importers:
.:
dependencies:
alpinejs:
- specifier: ^3.13.0
+ specifier: ^3.15.12
version: 3.16.2
devDependencies:
'@conventional-changelog/git-client':
diff --git a/src/utilities/anchor_block.js b/src/utilities/anchor_block.js
index 46bb920..d07b10b 100644
--- a/src/utilities/anchor_block.js
+++ b/src/utilities/anchor_block.js
@@ -46,8 +46,6 @@ export function anchor_block(el, template, { addScopeToNode, cleanup, destroyTre
}
},
delete() {
- // Not in mutateDom: Alpine before 3.14.2 removes nested x-if content
- // and x-for items without destroying them, leaving that to its observer
el._r_block = null;
delete el._x_currentIfEl;
delete el._x_lastRenderedEl;
diff --git a/tests/playwright/anchor-block.spec.js b/tests/playwright/anchor-block.spec.js
index f37ae31..395bdfc 100644
--- a/tests/playwright/anchor-block.spec.js
+++ b/tests/playwright/anchor-block.spec.js
@@ -56,7 +56,7 @@ for (const [directive, block] of Object.entries(blocks)) {
await expect(page.locator("dt, dd")).toHaveText(["Term", "Description"]);
expect(await probe(1)).toEqual({ destroyed: [], effects: [0, 1], events: 1 });
- // Alpine 3.14.2+ removes x-if and x-for clones this way, with its observer paused
+ // Alpine removes x-if and x-for clones this way, with its observer paused
await page.evaluate(() => Alpine.mutateDom(() => {
const anchor = document.querySelector("dl > template");
Alpine.destroyTree(anchor);
@@ -85,7 +85,6 @@ test("anchor block: removes nested x-if and x-for content", async ({ page }) =>
await expect(page.locator("b, i")).toHaveText(["1", "2", "3", "4"]);
- // Alpine before 3.14.2 leaves destroying the removed clones to its observer
await set_data(page.locator("div"), { show: false });
await expect(page.locator("b, i")).toHaveCount(0);
});
diff --git a/tests/playwright/assets/alpine.3.14.1.js b/tests/playwright/assets/alpine.3.15.12.js
similarity index 88%
rename from tests/playwright/assets/alpine.3.14.1.js
rename to tests/playwright/assets/alpine.3.15.12.js
index d25ad8f..3813721 100644
--- a/tests/playwright/assets/alpine.3.14.1.js
+++ b/tests/playwright/assets/alpine.3.15.12.js
@@ -4,9 +4,17 @@
var flushing = false;
var queue = [];
var lastFlushedIndex = -1;
+ var transactionActive = false;
function scheduler(callback) {
queueJob(callback);
}
+ function startTransaction() {
+ transactionActive = true;
+ }
+ function commitTransaction() {
+ transactionActive = false;
+ queueFlush();
+ }
function queueJob(job) {
if (!queue.includes(job))
queue.push(job);
@@ -19,6 +27,8 @@
}
function queueFlush() {
if (!flushing && !flushPending) {
+ if (transactionActive)
+ return;
flushPending = true;
queueMicrotask(flushJobs);
}
@@ -88,21 +98,33 @@
function watch(getter, callback) {
let firstTime = true;
let oldValue;
+ let oldValueJSON;
let effectReference = effect(() => {
let value = getter();
- JSON.stringify(value);
+ let newJSON = JSON.stringify(value);
if (!firstTime) {
- queueMicrotask(() => {
- callback(value, oldValue);
- oldValue = value;
- });
- } else {
- oldValue = value;
+ if (typeof value === "object" || value !== oldValue) {
+ let previousValue = typeof oldValue === "object" ? JSON.parse(oldValueJSON) : oldValue;
+ queueMicrotask(() => {
+ callback(value, previousValue);
+ });
+ }
}
+ oldValue = value;
+ oldValueJSON = newJSON;
firstTime = false;
});
return () => release(effectReference);
}
+ async function transaction(callback) {
+ startTransaction();
+ try {
+ await callback();
+ await Promise.resolve();
+ } finally {
+ commitTransaction();
+ }
+ }
// packages/alpinejs/src/mutation.js
var onAttributeAddeds = [];
@@ -142,10 +164,9 @@
});
}
function cleanupElement(el) {
- if (el._x_cleanups) {
- while (el._x_cleanups.length)
- el._x_cleanups.pop()();
- }
+ el._x_effects?.forEach(dequeueJob);
+ while (el._x_cleanups?.length)
+ el._x_cleanups.pop()();
}
var observer = new MutationObserver(onMutate);
var currentlyObserving = false;
@@ -193,7 +214,7 @@
deferredMutations = deferredMutations.concat(mutations);
return;
}
- let addedNodes = /* @__PURE__ */ new Set();
+ let addedNodes = [];
let removedNodes = /* @__PURE__ */ new Set();
let addedAttributes = /* @__PURE__ */ new Map();
let removedAttributes = /* @__PURE__ */ new Map();
@@ -201,8 +222,24 @@
if (mutations[i].target._x_ignoreMutationObserver)
continue;
if (mutations[i].type === "childList") {
- mutations[i].addedNodes.forEach((node) => node.nodeType === 1 && addedNodes.add(node));
- mutations[i].removedNodes.forEach((node) => node.nodeType === 1 && removedNodes.add(node));
+ mutations[i].removedNodes.forEach((node) => {
+ if (node.nodeType !== 1)
+ return;
+ if (!node._x_marker)
+ return;
+ removedNodes.add(node);
+ });
+ mutations[i].addedNodes.forEach((node) => {
+ if (node.nodeType !== 1)
+ return;
+ if (removedNodes.has(node)) {
+ removedNodes.delete(node);
+ return;
+ }
+ if (node._x_marker)
+ return;
+ addedNodes.push(node);
+ });
}
if (mutations[i].type === "attributes") {
let el = mutations[i].target;
@@ -235,29 +272,15 @@
onAttributeAddeds.forEach((i) => i(el, attrs));
});
for (let node of removedNodes) {
- if (addedNodes.has(node))
+ if (addedNodes.some((i) => i.contains(node)))
continue;
onElRemoveds.forEach((i) => i(node));
}
- addedNodes.forEach((node) => {
- node._x_ignoreSelf = true;
- node._x_ignore = true;
- });
for (let node of addedNodes) {
- if (removedNodes.has(node))
- continue;
if (!node.isConnected)
continue;
- delete node._x_ignoreSelf;
- delete node._x_ignore;
onElAddeds.forEach((i) => i(node));
- node._x_ignore = true;
- node._x_ignoreSelf = true;
}
- addedNodes.forEach((node) => {
- delete node._x_ignoreSelf;
- delete node._x_ignore;
- });
addedNodes = null;
removedNodes = null;
addedAttributes = null;
@@ -288,6 +311,13 @@
function mergeProxies(objects) {
return new Proxy({ objects }, mergeProxyTrap);
}
+ function keyInPrototypeChain(obj, key) {
+ if (obj === null || obj === Object.prototype)
+ return null;
+ if (Object.prototype.hasOwnProperty.call(obj, key))
+ return obj;
+ return keyInPrototypeChain(Object.getPrototypeOf(obj), key);
+ }
var mergeProxyTrap = {
ownKeys({ objects }) {
return Array.from(
@@ -313,9 +343,14 @@
);
},
set({ objects }, name, value, thisProxy) {
- const target = objects.find(
- (obj) => Object.prototype.hasOwnProperty.call(obj, name)
- ) || objects[objects.length - 1];
+ let target;
+ for (const obj of objects) {
+ target = keyInPrototypeChain(obj, name);
+ if (target)
+ break;
+ }
+ if (!target)
+ target = objects[objects.length - 1];
const descriptor = Object.getOwnPropertyDescriptor(target, name);
if (descriptor?.set && descriptor?.get)
return descriptor.set.call(thisProxy, value) || true;
@@ -332,7 +367,7 @@
// packages/alpinejs/src/interceptor.js
function initInterceptors(data2) {
- let isObject2 = (val) => typeof val === "object" && !Array.isArray(val) && val !== null;
+ let isObject3 = (val) => typeof val === "object" && !Array.isArray(val) && val !== null;
let recurse = (obj, basePath = "") => {
Object.entries(Object.getOwnPropertyDescriptors(obj)).forEach(([key, { value, enumerable }]) => {
if (enumerable === false || value === void 0)
@@ -343,7 +378,7 @@
if (typeof value === "object" && value !== null && value._x_interceptor) {
obj[key] = value.initialize(data2, path, key);
} else {
- if (isObject2(value) && value !== obj && !(value instanceof Element)) {
+ if (isObject3(value) && value !== obj && !(value instanceof Element)) {
recurse(value, path);
}
}
@@ -401,27 +436,23 @@
magics[name] = callback;
}
function injectMagics(obj, el) {
+ let memoizedUtilities = getUtilities(el);
Object.entries(magics).forEach(([name, callback]) => {
- let memoizedUtilities = null;
- function getUtilities() {
- if (memoizedUtilities) {
- return memoizedUtilities;
- } else {
- let [utilities, cleanup2] = getElementBoundUtilities(el);
- memoizedUtilities = { interceptor, ...utilities };
- onElRemoved(el, cleanup2);
- return memoizedUtilities;
- }
- }
Object.defineProperty(obj, `$${name}`, {
get() {
- return callback(el, getUtilities());
+ return callback(el, memoizedUtilities);
},
enumerable: false
});
});
return obj;
}
+ function getUtilities(el) {
+ let [utilities, cleanup2] = getElementBoundUtilities(el);
+ let utils = { interceptor, ...utilities };
+ onElRemoved(el, cleanup2);
+ return utils;
+ }
// packages/alpinejs/src/utils/error.js
function tryCatch(el, expression, callback, ...args) {
@@ -431,7 +462,14 @@
handleError(e, el, expression);
}
}
- function handleError(error2, el, expression = void 0) {
+ function handleError(...args) {
+ return errorHandler(...args);
+ }
+ var errorHandler = normalErrorHandler;
+ function setErrorHandler(handler4) {
+ errorHandler = handler4;
+ }
+ function normalErrorHandler(error2, el, expression = void 0) {
error2 = Object.assign(
error2 ?? { message: "No error message given." },
{ el, expression }
@@ -461,10 +499,15 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
function evaluateLater(...args) {
return theEvaluatorFunction(...args);
}
- var theEvaluatorFunction = normalEvaluator;
+ var theEvaluatorFunction = () => {
+ };
function setEvaluator(newEvaluator) {
theEvaluatorFunction = newEvaluator;
}
+ var theRawEvaluatorFunction;
+ function setRawEvaluator(newEvaluator) {
+ theRawEvaluatorFunction = newEvaluator;
+ }
function normalEvaluator(el, expression) {
let overriddenMagics = {};
injectMagics(overriddenMagics, el);
@@ -474,7 +517,11 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
function generateEvaluatorFromFunction(dataStack, func) {
return (receiver = () => {
- }, { scope: scope2 = {}, params = [] } = {}) => {
+ }, { scope: scope2 = {}, params = [], context } = {}) => {
+ if (!shouldAutoEvaluateFunctions) {
+ runIfTypeOfFunction(receiver, func, mergeProxies([scope2, ...dataStack]), params);
+ return;
+ }
let result = func.apply(mergeProxies([scope2, ...dataStack]), params);
runIfTypeOfFunction(receiver, result);
};
@@ -509,12 +556,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
function generateEvaluatorFromString(dataStack, expression, el) {
let func = generateFunctionFromString(expression, el);
return (receiver = () => {
- }, { scope: scope2 = {}, params = [] } = {}) => {
+ }, { scope: scope2 = {}, params = [], context } = {}) => {
func.result = void 0;
func.finished = false;
let completeScope = mergeProxies([scope2, ...dataStack]);
if (typeof func === "function") {
- let promise = func(func, completeScope).catch((error2) => handleError(error2, el, expression));
+ let promise = func.call(context, func, completeScope).catch((error2) => handleError(error2, el, expression));
if (func.finished) {
runIfTypeOfFunction(receiver, func.result, completeScope, params, el);
func.result = void 0;
@@ -540,6 +587,38 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
receiver(value);
}
}
+ function evaluateRaw(...args) {
+ return theRawEvaluatorFunction(...args);
+ }
+ function normalRawEvaluator(el, expression, extras = {}) {
+ let overriddenMagics = {};
+ injectMagics(overriddenMagics, el);
+ let dataStack = [overriddenMagics, ...closestDataStack(el)];
+ let scope2 = mergeProxies([extras.scope ?? {}, ...dataStack]);
+ let params = extras.params ?? [];
+ if (expression.includes("await")) {
+ let AsyncFunction = Object.getPrototypeOf(async function() {
+ }).constructor;
+ let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression.trim()) || /^(let|const)\s/.test(expression.trim()) ? `(async()=>{ ${expression} })()` : expression;
+ let func = new AsyncFunction(
+ ["scope"],
+ `with (scope) { let __result = ${rightSideSafeExpression}; return __result }`
+ );
+ let result = func.call(extras.context, scope2);
+ return result;
+ } else {
+ let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression.trim()) || /^(let|const)\s/.test(expression.trim()) ? `(()=>{ ${expression} })()` : expression;
+ let func = new Function(
+ ["scope"],
+ `with (scope) { let __result = ${rightSideSafeExpression}; return __result }`
+ );
+ let result = func.call(extras.context, scope2);
+ if (typeof result === "function" && shouldAutoEvaluateFunctions) {
+ return result.apply(scope2, params);
+ }
+ return result;
+ }
+ }
// packages/alpinejs/src/directives.js
var prefixAsString = "x-";
@@ -669,6 +748,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
var alpineAttributeRegex = () => new RegExp(`^${prefixAsString}([^:^.]+)\\b`);
function toParsedDirectives(transformedAttributeMap, originalAttributeOverride) {
return ({ name, value }) => {
+ if (name === value)
+ value = "";
let typeMatch = name.match(alpineAttributeRegex());
let valueMatch = name.match(/:([a-zA-Z0-9\-_:]+)/);
let modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || [];
@@ -686,8 +767,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
var directiveOrder = [
"ignore",
"ref",
- "data",
"id",
+ "data",
"anchor",
"bind",
"init",
@@ -707,14 +788,16 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
// packages/alpinejs/src/utils/dispatch.js
- function dispatch(el, name, detail = {}) {
- el.dispatchEvent(
+ function dispatch(el, name, detail = {}, options = {}) {
+ return el.dispatchEvent(
new CustomEvent(name, {
detail,
bubbles: true,
// Allows events to pass the shadow DOM barrier.
composed: true,
- cancelable: true
+ cancelable: true,
+ // Allows overriding the default event options.
+ ...options
})
);
}
@@ -793,7 +876,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
if (callback(el))
return el;
if (el._x_teleportBack)
- el = el._x_teleportBack;
+ return findClosest(el._x_teleportBack, callback);
+ if (el.parentNode instanceof ShadowRoot) {
+ return findClosest(el.parentNode.host, callback);
+ }
if (!el.parentElement)
return;
return findClosest(el.parentElement, callback);
@@ -805,21 +891,29 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
function interceptInit(callback) {
initInterceptors2.push(callback);
}
+ var markerDispenser = 1;
function initTree(el, walker = walk, intercept = () => {
}) {
+ if (findClosest(el, (i) => i._x_ignore))
+ return;
deferHandlingDirectives(() => {
walker(el, (el2, skip) => {
+ if (el2._x_marker)
+ return;
intercept(el2, skip);
initInterceptors2.forEach((i) => i(el2, skip));
directives(el2, el2.attributes).forEach((handle) => handle());
+ if (!el2._x_ignore)
+ el2._x_marker = markerDispenser++;
el2._x_ignore && skip();
});
});
}
function destroyTree(root, walker = walk) {
walker(root, (el) => {
- cleanupAttributes(el);
cleanupElement(el);
+ cleanupAttributes(el);
+ delete el._x_marker;
});
}
function warnAboutMissingPlugins() {
@@ -877,9 +971,11 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
return setClassesFromString(el, value);
}
+ function splitClasses(classString) {
+ return classString.split(/\s/).filter(Boolean);
+ }
function setClassesFromString(el, classString) {
- let split = (classString2) => classString2.split(" ").filter(Boolean);
- let missingClasses = (classString2) => classString2.split(" ").filter((i) => !el.classList.contains(i)).filter(Boolean);
+ let missingClasses = (classString2) => splitClasses(classString2).filter((i) => !el.classList.contains(i)).filter(Boolean);
let addClassesAndReturnUndo = (classes) => {
el.classList.add(...classes);
return () => {
@@ -890,9 +986,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
return addClassesAndReturnUndo(missingClasses(classString));
}
function setClassesFromObject(el, classObject) {
- let split = (classString) => classString.split(" ").filter(Boolean);
- let forAdd = Object.entries(classObject).flatMap(([classString, bool]) => bool ? split(classString) : false).filter(Boolean);
- let forRemove = Object.entries(classObject).flatMap(([classString, bool]) => !bool ? split(classString) : false).filter(Boolean);
+ let forAdd = Object.entries(classObject).flatMap(([classString, bool]) => bool ? splitClasses(classString) : false).filter(Boolean);
+ let forRemove = Object.entries(classObject).flatMap(([classString, bool]) => !bool ? splitClasses(classString) : false).filter(Boolean);
let added = [];
let removed = [];
forRemove.forEach((i) => {
@@ -1326,18 +1421,11 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
}
function bindInputValue(el, value) {
- if (el.type === "radio") {
+ if (isRadio(el)) {
if (el.attributes.value === void 0) {
el.value = value;
}
- if (window.fromModel) {
- if (typeof value === "boolean") {
- el.checked = safeParseBoolean(el.value) === value;
- } else {
- el.checked = checkedAttrLooseCompare(el.value, value);
- }
- }
- } else if (el.type === "checkbox") {
+ } else if (isCheckbox(el)) {
if (Number.isInteger(value)) {
el.value = value;
} else if (!Array.isArray(value) && typeof value !== "boolean" && ![null, void 0].includes(value)) {
@@ -1413,34 +1501,37 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
return rawValue ? Boolean(rawValue) : null;
}
+ var booleanAttributes = /* @__PURE__ */ new Set([
+ "allowfullscreen",
+ "async",
+ "autofocus",
+ "autoplay",
+ "checked",
+ "controls",
+ "default",
+ "defer",
+ "disabled",
+ "formnovalidate",
+ "inert",
+ "ismap",
+ "itemscope",
+ "loop",
+ "multiple",
+ "muted",
+ "nomodule",
+ "novalidate",
+ "open",
+ "playsinline",
+ "readonly",
+ "required",
+ "reversed",
+ "selected",
+ "shadowrootclonable",
+ "shadowrootdelegatesfocus",
+ "shadowrootserializable"
+ ]);
function isBooleanAttr(attrName) {
- const booleanAttributes = [
- "disabled",
- "checked",
- "required",
- "readonly",
- "open",
- "selected",
- "autofocus",
- "itemscope",
- "multiple",
- "novalidate",
- "allowfullscreen",
- "allowpaymentrequest",
- "formnovalidate",
- "autoplay",
- "controls",
- "loop",
- "muted",
- "playsinline",
- "default",
- "ismap",
- "reversed",
- "async",
- "defer",
- "nomodule"
- ];
- return booleanAttributes.includes(attrName);
+ return booleanAttributes.has(attrName);
}
function attributeShouldntBePreservedIfFalsy(name) {
return !["aria-pressed", "aria-checked", "aria-expanded", "aria-selected"].includes(name);
@@ -1473,13 +1564,19 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
return attr;
}
+ function isCheckbox(el) {
+ return el.type === "checkbox" || el.localName === "ui-checkbox" || el.localName === "ui-switch";
+ }
+ function isRadio(el) {
+ return el.type === "radio" || el.localName === "ui-radio";
+ }
// packages/alpinejs/src/utils/debounce.js
function debounce(func, wait) {
- var timeout;
+ let timeout;
return function() {
- var context = this, args = arguments;
- var later = function() {
+ const context = this, args = arguments;
+ const later = function() {
timeout = null;
func.apply(context, args);
};
@@ -1551,10 +1648,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
return stores[name];
}
stores[name] = value;
+ initInterceptors(stores[name]);
if (typeof value === "object" && value !== null && value.hasOwnProperty("init") && typeof value.init === "function") {
stores[name].init();
}
- initInterceptors(stores[name]);
}
function getStores() {
return stores;
@@ -1642,7 +1739,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
get raw() {
return raw;
},
- version: "3.14.1",
+ get transaction() {
+ return transaction;
+ },
+ version: "3.15.12",
flushAndStopDeferringMutations,
dontAutoEvaluateFunctions,
disableEffectScheduling,
@@ -1656,13 +1756,17 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
onlyDuringClone,
addRootSelector,
addInitSelector,
+ setErrorHandler,
interceptClone,
addScopeToNode,
deferMutations,
mapAttributes,
evaluateLater,
interceptInit,
+ initInterceptors,
+ injectMagics,
setEvaluator,
+ setRawEvaluator,
mergeProxies,
extractProp,
findClosest,
@@ -1681,6 +1785,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
throttle,
debounce,
evaluate,
+ evaluateRaw,
initTree,
nextTick,
prefixed: prefix,
@@ -2528,7 +2633,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
return;
el._x_removeModelListeners["default"]();
let outerGet = el._x_model.get;
- let outerSet = el._x_model.set;
+ let outerSet = el._x_model.setWithModifiers;
let releaseEntanglement = entangle(
{
get() {
@@ -2580,10 +2685,9 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
};
mutateDom(() => {
- placeInDom(clone2, target, modifiers);
skipDuringClone(() => {
+ placeInDom(clone2, target, modifiers);
initTree(clone2);
- clone2._x_ignore = true;
})();
});
el._x_teleportPutBack = () => {
@@ -2592,7 +2696,12 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
placeInDom(el._x_teleport, target2, modifiers);
});
};
- cleanup2(() => clone2.remove());
+ cleanup2(
+ () => mutateDom(() => {
+ clone2.remove();
+ destroyTree(clone2);
+ })
+ );
});
var teleportContainerDuringClone = document.createElement("div");
function getTarget(expression) {
@@ -2632,24 +2741,16 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
event = dotSyntax(event);
if (modifiers.includes("camel"))
event = camelCase2(event);
- if (modifiers.includes("passive"))
- options.passive = true;
if (modifiers.includes("capture"))
options.capture = true;
if (modifiers.includes("window"))
listenerTarget = window;
if (modifiers.includes("document"))
listenerTarget = document;
- 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;
- handler4 = debounce(handler4, 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;
- handler4 = throttle(handler4, wait);
+ if (modifiers.includes("passive")) {
+ options.passive = modifiers[modifiers.indexOf("passive") + 1] !== "false";
}
+ handler4 = addDebounceOrThrottle(modifiers, handler4);
if (modifiers.includes("prevent"))
handler4 = wrapHandler(handler4, (next, e) => {
e.preventDefault();
@@ -2684,6 +2785,14 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
handler4 = wrapHandler(handler4, (next, e) => {
e.target === el && next(e);
});
+ if (event === "submit") {
+ handler4 = wrapHandler(handler4, (next, e) => {
+ if (e.target._x_pendingModelUpdates) {
+ e.target._x_pendingModelUpdates.forEach((fn) => fn());
+ }
+ next(e);
+ });
+ }
if (isKeyEvent(event) || isClickEvent(event)) {
handler4 = wrapHandler(handler4, (next, e) => {
if (isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers)) {
@@ -2697,6 +2806,19 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
listenerTarget.removeEventListener(event, handler4, options);
};
}
+ function addDebounceOrThrottle(modifiers, handler4) {
+ 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;
+ handler4 = debounce(handler4, 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;
+ handler4 = throttle(handler4, wait);
+ }
+ return handler4;
+ }
function dotSyntax(subject) {
return subject.replace(/-/g, ".");
}
@@ -2721,7 +2843,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
function isListeningForASpecificKeyThatHasntBeenPressed(e, modifiers) {
let keyModifiers = modifiers.filter((i) => {
- return !["window", "document", "prevent", "stop", "once", "capture", "self", "away", "outside", "passive"].includes(i);
+ return !["window", "document", "prevent", "stop", "once", "capture", "self", "away", "outside", "passive", "preserve-scroll", "blur", "change", "lazy"].includes(i);
});
if (keyModifiers.includes("debounce")) {
let debounceIndex = keyModifiers.indexOf("debounce");
@@ -2785,7 +2907,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
directive("model", (el, { modifiers, expression }, { effect: effect3, cleanup: cleanup2 }) => {
let scopeTarget = el;
if (modifiers.includes("parent")) {
- scopeTarget = el.parentNode;
+ scopeTarget = findClosest(el, (element) => element !== el);
}
let evaluateGet = evaluateLater(scopeTarget, expression);
let evaluateSet;
@@ -2820,13 +2942,50 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
el.setAttribute("name", expression);
});
}
- var event = el.tagName.toLowerCase() === "select" || ["checkbox", "radio"].includes(el.type) || modifiers.includes("lazy") ? "change" : "input";
- let removeListener = isCloning ? () => {
- } : on(el, event, modifiers, (e) => {
- setValue(getInputValue(el, modifiers, e, getValue()));
- });
+ let hasChangeModifier = modifiers.includes("change") || modifiers.includes("lazy");
+ let hasBlurModifier = modifiers.includes("blur");
+ let hasEnterModifier = modifiers.includes("enter");
+ let hasExplicitEventModifiers = hasChangeModifier || hasBlurModifier || hasEnterModifier;
+ let removeListener;
+ if (isCloning) {
+ removeListener = () => {
+ };
+ } else if (hasExplicitEventModifiers) {
+ let listeners = [];
+ let syncValue = (e) => setValue(getInputValue(el, modifiers, e, getValue()));
+ if (hasChangeModifier) {
+ listeners.push(on(el, "change", modifiers, syncValue));
+ }
+ if (hasBlurModifier) {
+ listeners.push(on(el, "blur", modifiers, syncValue));
+ if (el.form) {
+ let form = el.form;
+ let syncCallback = () => syncValue({ target: el });
+ if (!form._x_pendingModelUpdates)
+ form._x_pendingModelUpdates = [];
+ form._x_pendingModelUpdates.push(syncCallback);
+ cleanup2(() => {
+ if (form._x_pendingModelUpdates) {
+ form._x_pendingModelUpdates.splice(form._x_pendingModelUpdates.indexOf(syncCallback), 1);
+ }
+ });
+ }
+ }
+ if (hasEnterModifier) {
+ listeners.push(on(el, "keydown", modifiers, (e) => {
+ if (e.key === "Enter")
+ syncValue(e);
+ }));
+ }
+ removeListener = () => listeners.forEach((remove) => remove());
+ } else {
+ let event = el.tagName.toLowerCase() === "select" || ["checkbox", "radio"].includes(el.type) ? "change" : "input";
+ removeListener = on(el, event, modifiers, (e) => {
+ setValue(getInputValue(el, modifiers, e, getValue()));
+ });
+ }
if (modifiers.includes("fill")) {
- if ([void 0, null, ""].includes(getValue()) || el.type === "checkbox" && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
+ if ([void 0, null, ""].includes(getValue()) || isCheckbox(el) && Array.isArray(getValue()) || el.tagName.toLowerCase() === "select" && el.multiple) {
setValue(
getInputValue(el, modifiers, { target: el }, getValue())
);
@@ -2848,14 +3007,29 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
},
set(value) {
setValue(value);
- }
+ },
+ setWithModifiers: addDebounceOrThrottle(modifiers, setValue)
};
el._x_forceModelUpdate = (value) => {
if (value === void 0 && typeof expression === "string" && expression.match(/\./))
value = "";
- window.fromModel = true;
- mutateDom(() => bind(el, "value", value));
- delete window.fromModel;
+ mutateDom(() => {
+ if (isCheckbox(el)) {
+ if (Array.isArray(value)) {
+ el.checked = value.some((val) => val == el.value);
+ } else {
+ el.checked = !!value;
+ }
+ } else if (isRadio(el)) {
+ if (typeof value === "boolean") {
+ el.checked = safeParseBoolean(el.value) === value;
+ } else {
+ el.checked = el.value == value;
+ }
+ } else {
+ bind(el, "value", value);
+ }
+ });
};
effect3(() => {
let value = getValue();
@@ -2868,7 +3042,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
return mutateDom(() => {
if (event instanceof CustomEvent && event.detail !== void 0)
return event.detail !== null && event.detail !== void 0 ? event.detail : event.target.value;
- else if (el.type === "checkbox") {
+ else if (isCheckbox(el)) {
if (Array.isArray(currentValue)) {
let newValue = null;
if (modifiers.includes("number")) {
@@ -2899,7 +3073,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
} else {
let newValue;
- if (el.type === "radio") {
+ if (isRadio(el)) {
if (event.target.checked) {
newValue = event.target.value;
} else {
@@ -2964,7 +3138,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
effect3(() => {
evaluate2((value) => {
mutateDom(() => {
- el.innerHTML = value;
+ el.innerHTML = value ?? "";
el._x_ignoreSelf = true;
initTree(el);
delete el._x_ignoreSelf;
@@ -3111,131 +3285,92 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
// the x-bind:key expression is stored for our use instead of evaluated.
el._x_keyExpression || "index"
);
- el._x_prevKeys = [];
- el._x_lookup = {};
+ el._x_lookup = /* @__PURE__ */ new Map();
effect3(() => loop(el, iteratorNames, evaluateItems, evaluateKey));
cleanup2(() => {
- Object.values(el._x_lookup).forEach((el2) => el2.remove());
- delete el._x_prevKeys;
+ el._x_lookup.forEach(
+ (el2) => mutateDom(() => {
+ destroyTree(el2);
+ el2.remove();
+ })
+ );
delete el._x_lookup;
});
});
- function loop(el, iteratorNames, evaluateItems, evaluateKey) {
- let isObject2 = (i) => typeof i === "object" && !Array.isArray(i);
- let templateEl = el;
+ function refreshScope(scope2) {
+ return (newScope) => {
+ Object.entries(newScope).forEach(([key, value]) => {
+ scope2[key] = value;
+ });
+ };
+ }
+ function loop(templateEl, iteratorNames, evaluateItems, evaluateKey) {
evaluateItems((items) => {
- if (isNumeric3(items) && items >= 0) {
- items = Array.from(Array(items).keys(), (i) => i + 1);
- }
- if (items === void 0)
+ if (isNumeric3(items))
+ items = Array.from({ length: items }, (_, i) => i + 1);
+ if (items === void 0 || items === null)
items = [];
- let lookup = el._x_lookup;
- let prevKeys = el._x_prevKeys;
- let scopes = [];
- let keys = [];
- if (isObject2(items)) {
- items = Object.entries(items).map(([key, value]) => {
- let scope2 = getIterationScopeVariables(iteratorNames, value, key, items);
- evaluateKey((value2) => {
- if (keys.includes(value2))
- warn("Duplicate key on x-for", el);
- keys.push(value2);
- }, { scope: { index: key, ...scope2 } });
- scopes.push(scope2);
- });
- } else {
- for (let i = 0; i < items.length; i++) {
- let scope2 = getIterationScopeVariables(iteratorNames, items[i], i, items);
- evaluateKey((value) => {
- if (keys.includes(value))
- warn("Duplicate key on x-for", el);
- keys.push(value);
- }, { scope: { index: i, ...scope2 } });
- scopes.push(scope2);
- }
- }
- let adds = [];
- let moves = [];
- let removes = [];
- let sames = [];
- for (let i = 0; i < prevKeys.length; i++) {
- let key = prevKeys[i];
- if (keys.indexOf(key) === -1)
- removes.push(key);
- }
- prevKeys = prevKeys.filter((key) => !removes.includes(key));
- let lastKey = "template";
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- let prevIndex = prevKeys.indexOf(key);
- if (prevIndex === -1) {
- prevKeys.splice(i, 0, key);
- adds.push([lastKey, i]);
- } else if (prevIndex !== i) {
- let keyInSpot = prevKeys.splice(i, 1)[0];
- let keyForSpot = prevKeys.splice(prevIndex - 1, 1)[0];
- prevKeys.splice(i, 0, keyForSpot);
- prevKeys.splice(prevIndex, 0, keyInSpot);
- moves.push([keyInSpot, keyForSpot]);
- } else {
- sames.push(key);
- }
- lastKey = key;
- }
- for (let i = 0; i < removes.length; i++) {
- let key = removes[i];
- if (!!lookup[key]._x_effects) {
- lookup[key]._x_effects.forEach(dequeueJob);
- }
- lookup[key].remove();
- lookup[key] = null;
- delete lookup[key];
- }
- for (let i = 0; i < moves.length; i++) {
- let [keyInSpot, keyForSpot] = moves[i];
- let elInSpot = lookup[keyInSpot];
- let elForSpot = lookup[keyForSpot];
- let marker = document.createElement("div");
- mutateDom(() => {
- if (!elForSpot)
- warn(`x-for ":key" is undefined or invalid`, templateEl, keyForSpot, lookup);
- elForSpot.after(marker);
- elInSpot.after(elForSpot);
- elForSpot._x_currentIfEl && elForSpot.after(elForSpot._x_currentIfEl);
- marker.before(elInSpot);
- elInSpot._x_currentIfEl && elInSpot.after(elInSpot._x_currentIfEl);
- marker.remove();
+ if (items instanceof Set)
+ items = Array.from(items);
+ if (items instanceof Map)
+ items = Array.from(items);
+ let oldLookup = templateEl._x_lookup;
+ let lookup = /* @__PURE__ */ new Map();
+ templateEl._x_lookup = lookup;
+ let hasStringKeys = isObject2(items);
+ let scopeEntries = Object.entries(items).map(([index, item]) => {
+ if (!hasStringKeys)
+ index = parseInt(index);
+ let scope2 = getIterationScopeVariables(iteratorNames, item, index, items);
+ let key;
+ evaluateKey((innerKey) => {
+ if (typeof innerKey === "object")
+ warn("x-for key cannot be an object, it must be a string or an integer", templateEl);
+ if (oldLookup.has(innerKey)) {
+ lookup.set(innerKey, oldLookup.get(innerKey));
+ oldLookup.delete(innerKey);
+ }
+ key = innerKey;
+ }, { scope: { index, ...scope2 } });
+ return [key, scope2];
+ });
+ mutateDom(() => {
+ oldLookup.forEach((el) => {
+ destroyTree(el);
+ el.remove();
});
- elForSpot._x_refreshXForScope(scopes[keys.indexOf(keyForSpot)]);
- }
- for (let i = 0; i < adds.length; i++) {
- let [lastKey2, index] = adds[i];
- let lastEl = lastKey2 === "template" ? templateEl : lookup[lastKey2];
- if (lastEl._x_currentIfEl)
- lastEl = lastEl._x_currentIfEl;
- let scope2 = scopes[index];
- let key = keys[index];
- let clone2 = document.importNode(templateEl.content, true).firstElementChild;
- let reactiveScope = reactive(scope2);
- addScopeToNode(clone2, reactiveScope, templateEl);
- clone2._x_refreshXForScope = (newScope) => {
- Object.entries(newScope).forEach(([key2, value]) => {
- reactiveScope[key2] = value;
- });
- };
- mutateDom(() => {
- lastEl.after(clone2);
- skipDuringClone(() => initTree(clone2))();
+ let added = /* @__PURE__ */ new Set();
+ let prev = templateEl;
+ scopeEntries.forEach(([key, scope2]) => {
+ if (lookup.has(key)) {
+ let el = lookup.get(key);
+ el._x_refreshXForScope(scope2);
+ if (prev.nextElementSibling !== el) {
+ if (prev.nextElementSibling)
+ el.replaceWith(prev.nextElementSibling);
+ prev.after(el);
+ }
+ prev = el;
+ if (el._x_currentIfEl) {
+ if (el.nextElementSibling !== el._x_currentIfEl)
+ prev.after(el._x_currentIfEl);
+ prev = el._x_currentIfEl;
+ }
+ return;
+ }
+ if (templateEl.content.children.length > 1)
+ warn("x-for templates require a single root element, additional elements will be ignored.", templateEl);
+ let clone2 = document.importNode(templateEl.content, true).firstElementChild;
+ let reactiveScope = reactive(scope2);
+ addScopeToNode(clone2, reactiveScope, templateEl);
+ clone2._x_refreshXForScope = refreshScope(reactiveScope);
+ lookup.set(key, clone2);
+ added.add(clone2);
+ prev.after(clone2);
+ prev = clone2;
});
- if (typeof key === "object") {
- warn("x-for key cannot be an object, it must be a string or an integer", templateEl);
- }
- lookup[key] = clone2;
- }
- for (let i = 0; i < sames.length; i++) {
- lookup[sames[i]]._x_refreshXForScope(scopes[keys.indexOf(sames[i])]);
- }
- templateEl._x_prevKeys = keys;
+ skipDuringClone(() => added.forEach((clone2) => initTree(clone2)))();
+ });
});
}
function parseForExpression(expression) {
@@ -3282,7 +3417,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
return scopeVariables;
}
function isNumeric3(subject) {
- return !Array.isArray(subject) && !isNaN(subject);
+ return typeof subject !== "object" && !isNaN(subject);
+ }
+ function isObject2(subject) {
+ return typeof subject === "object" && !Array.isArray(subject);
}
// packages/alpinejs/src/directives/x-ref.js
@@ -3290,6 +3428,8 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
}
handler3.inline = (el, { expression }, { cleanup: cleanup2 }) => {
let root = closestRoot(el);
+ if (!root)
+ return;
if (!root._x_refs)
root._x_refs = {};
root._x_refs[expression] = el;
@@ -3313,12 +3453,10 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
});
el._x_currentIfEl = clone2;
el._x_undoIf = () => {
- walk(clone2, (node) => {
- if (!!node._x_effects) {
- node._x_effects.forEach(dequeueJob);
- }
+ mutateDom(() => {
+ destroyTree(clone2);
+ clone2.remove();
});
- clone2.remove();
delete el._x_currentIfEl;
};
return clone2;
@@ -3375,6 +3513,7 @@ ${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el);
// packages/alpinejs/src/index.js
alpine_default.setEvaluator(normalEvaluator);
+ alpine_default.setRawEvaluator(normalRawEvaluator);
alpine_default.setReactivityEngine({ reactive: reactive2, effect: effect2, release: stop, raw: toRaw });
var src_default = alpine_default;
diff --git a/tests/playwright/assets/page.html b/tests/playwright/assets/page.html
index e39714e..33a850d 100644
--- a/tests/playwright/assets/page.html
+++ b/tests/playwright/assets/page.html
@@ -21,6 +21,6 @@
-
+