@ramstack/alpinegear-main
Version:
@ramstack/alpinegear-main is a combined plugin that includes several Alpine.js directives, providing a convenient all-in-one package.
703 lines (540 loc) • 23.1 kB
JavaScript
(function () {
'use strict';
function create_getter(evaluate_later, ...args) {
const evaluate = evaluate_later(...args);
return () => {
let result;
evaluate(v => result = v);
return has_getter(result) ? result.get() : result;
};
}
function create_setter(evaluate_later, ...args) {
const evaluate = evaluate_later(...args);
args[args.length - 1] = `${ args.at(-1) } = __val`;
const set = evaluate_later(...args);
return value => {
let result;
evaluate(v => result = v);
if (has_setter(result)) {
result.set(value);
}
else {
set(() => { }, {
scope: {
__val: value
}
});
}
};
}
function has_getter(value) {
return typeof value?.get === "function";
}
function has_setter(value) {
return typeof value?.set === "function";
}
const key = Symbol();
let observer;
function observe_resize(el, listener) {
observer ??= new ResizeObserver(entries => {
for (const e of entries) {
for (const callback of e.target[key]?.values() ?? []) {
callback(e);
}
}
});
el[key] ??= new Set();
el[key].add(listener);
observer.observe(el);
return () => {
el[key].delete(listener);
if (!el[key].size) {
observer.unobserve(el);
el[key] = null;
}
};
}
const warn = (...args) => console.warn("alpinegear.js:", ...args);
const is_array = Array.isArray;
const is_nullish = value => value === null || value === undefined;
const is_checkable_input = el => el.type === "checkbox" || el.type === "radio";
const is_numeric_input = el => el.type === "number" || el.type === "range";
const is_template = el => el instanceof HTMLTemplateElement;
const is_element = el => el.nodeType === Node.ELEMENT_NODE;
const as_array = value => is_array(value) ? value : [value];
const loose_equal = (a, b) => a == b;
const loose_index_of = (array, value) => array.findIndex(v => v == value);
const has_modifier = (modifiers, modifier) => modifiers.includes(modifier);
function assert(value, message) {
if (!value) {
throw new Error(message);
}
}
const listen = (target, type, listener, options) => {
target.addEventListener(type, listener, options);
return () => target.removeEventListener(type, listener, options);
};
const clone = value =>
typeof value === "object"
? JSON.parse(JSON.stringify(value))
: value;
const closest = (el, callback) => {
while (el && !callback(el)) {
el = (el._x_teleportBack ?? el).parentElement;
}
return el;
};
const create_map = keys => new Map(
keys.split(",").map(v => [v.trim().toLowerCase(), v.trim()]));
function watch(get_value, callback, options = null) {
assert(Alpine, "Alpine is not defined");
const {
effect,
release
} = Alpine;
let new_value;
let old_value;
let initialized = false;
const handle = effect(() => {
new_value = get_value();
if (!initialized) {
options?.deep && JSON.stringify(new_value);
old_value = new_value;
}
if (initialized || (options?.immediate ?? true)) {
setTimeout(() => {
callback(new_value, old_value);
old_value = new_value;
}, 0);
}
initialized = true;
});
return () => release(handle);
}
const canonical_names = create_map(
"value,checked,files," +
"innerHTML,innerText,textContent," +
"videoHeight,videoWidth," +
"naturalHeight,naturalWidth," +
"clientHeight,clientWidth,offsetHeight,offsetWidth," +
"indeterminate," +
"open," +
"group");
function plugin$5({ directive, entangle, evaluateLater, mapAttributes, mutateDom, prefixed }) {
mapAttributes(attr => ({
name: attr.name.replace(/^&/, prefixed("bound:")),
value: attr.value
}));
directive("bound", (el, { expression, value, modifiers }, { effect, cleanup }) => {
if (!value) {
warn("x-bound directive expects the presence of a bound property name");
return;
}
const tag_name = el.tagName.toUpperCase();
expression = expression?.trim();
const property_name = canonical_names.get(value.trim().replace("-", "").toLowerCase());
// if the expression is omitted, then we assume it corresponds
// to the bound property name, allowing us to write expressions more concisely,
// and write &value instead of &value="value"
expression ||= property_name;
const get_value = create_getter(evaluateLater, el, expression);
const set_value = create_setter(evaluateLater, el, expression);
const update_property = () => loose_equal(el[property_name], get_value()) || mutateDom(() => el[property_name] = get_value());
const update_variable = () => set_value(is_numeric_input(el) ? to_number(el[property_name]) : el[property_name]);
let processed;
switch (property_name) {
case "value":
process_value();
break;
case "checked":
process_checked();
break;
case "files":
process_files();
break;
case "innerHTML":
case "innerText":
case "textContent":
process_contenteditable();
break;
case "videoHeight":
case "videoWidth":
process_media_resize("VIDEO", "resize");
break;
case "naturalHeight":
case "naturalWidth":
process_media_resize("IMG", "load");
break;
case "clientHeight":
case "clientWidth":
case "offsetHeight":
case "offsetWidth":
process_dimensions();
break;
case "indeterminate":
process_indeterminate();
break;
case "open":
process_details();
break;
case "group":
process_group();
break;
}
if (!processed) {
const modifier =
has_modifier(modifiers, "in") ? "in" :
has_modifier(modifiers, "out") ? "out" : "inout";
const source_el = expression === value
? closest(el.parentNode, node => node._x_dataStack)
: el;
if (!el._x_dataStack) {
warn("x-bound directive requires the presence of the x-data directive to bind component properties");
return;
}
if (!source_el) {
warn(`x-bound directive cannot find the parent scope where the '${ value }' property is defined`);
return;
}
const source = {
get: create_getter(evaluateLater, source_el, expression),
set: create_setter(evaluateLater, source_el, expression)
};
const target = {
get: create_getter(evaluateLater, el, value),
set: create_setter(evaluateLater, el, value)
};
switch (modifier) {
case "in":
cleanup(watch(() => source.get(), v => target.set(clone(v))));
break;
case "out":
cleanup(watch(() => target.get(), v => source.set(clone(v))));
break;
default:
cleanup(entangle(source, target));
break;
}
}
function process_value() {
switch (tag_name) {
case "INPUT":
case "TEXTAREA":
is_nullish(get_value()) && update_variable();
effect(update_property);
cleanup(listen(el, "input", update_variable));
processed = true;
break;
case "SELECT":
setTimeout(() => {
is_nullish(get_value()) && update_variable();
effect(() => apply_select_values(el, as_array(get_value() ?? [])));
cleanup(listen(el, "change", () => set_value(collect_selected_values(el))));
}, 0);
processed = true;
break;
}
}
function process_checked() {
if (is_checkable_input(el)) {
effect(update_property);
cleanup(listen(el, "change", update_variable));
processed = true;
}
}
function process_indeterminate() {
if (el.type === "checkbox") {
is_nullish(get_value()) && update_variable();
effect(update_property);
cleanup(listen(el, "change", update_variable));
processed = true;
}
}
function process_files() {
if (el.type === "file") {
get_value() instanceof FileList || update_variable();
effect(update_property);
cleanup(listen(el, "input", update_variable));
processed = true;
}
}
function process_contenteditable() {
if (el.contentEditable === "true") {
is_nullish(get_value()) && update_variable();
effect(update_property);
cleanup(listen(el, "input", update_variable));
processed = true;
}
}
function process_media_resize(name, event_name) {
if (tag_name === name) {
update_variable();
cleanup(listen(el, event_name, update_variable));
processed = true;
}
}
function process_dimensions() {
cleanup(observe_resize(el, update_variable));
processed = true;
}
function process_details() {
if (tag_name === "DETAILS") {
is_nullish(get_value()) && update_variable();
effect(update_property);
cleanup(listen(el, "toggle", update_variable));
processed = true;
}
}
function process_group() {
if (is_checkable_input(el)) {
el.name || mutateDom(() => el.name = expression);
effect(() =>
mutateDom(() =>
apply_group_values(el, get_value() ?? [])));
cleanup(listen(el, "input", () => set_value(collect_group_values(el, get_value()))));
processed = true;
}
}
});
}
function to_number(value) {
return value === "" ? null : +value;
}
function apply_select_values(el, values) {
for (const option of el.options) {
option.selected = loose_index_of(values, option.value) >= 0;
}
}
function collect_selected_values(el) {
if (el.multiple) {
return [...el.selectedOptions].map(o => o.value);
}
return el.value;
}
function apply_group_values(el, values) {
el.checked = is_array(values)
? loose_index_of(values, el.value) >= 0
: loose_equal(el.value, values);
}
function collect_group_values(el, values) {
if (el.type === "radio") {
return el.value;
}
values = as_array(values);
const index = loose_index_of(values, el.value);
if (el.checked) {
index >= 0 || values.push(el.value);
}
else {
index >= 0 && values.splice(index, 1);
}
return values;
}
function plugin$4({ directive, evaluateLater, mutateDom }) {
directive("format", (el, { modifiers }, { effect }) => {
const placeholder_regex = /{{(?<expr>.+?)}}/g;
const is_once = has_modifier(modifiers, "once");
const has_format_attr = el => el.hasAttribute("x-format");
process(el);
function update(callback) {
if (is_once) {
mutateDom(() => callback());
}
else {
effect(() => mutateDom(() => callback()));
}
}
function process(node) {
switch (node.nodeType) {
case Node.TEXT_NODE:
process_text_node(node);
break;
case Node.ELEMENT_NODE:
if (node !== el) {
if (node.hasAttribute("x-data") && !has_format_attr(node)) {
node.setAttribute("x-format", "");
}
if (has_format_attr(node)) {
break;
}
}
process_nodes(node);
process_attributes(node);
break;
}
}
function process_text_node(node) {
const tokens = node.textContent.split(placeholder_regex);
if (tokens.length > 1) {
const fragment = new DocumentFragment();
for (let i = 0; i < tokens.length; i++) {
if ((i % 2) === 0) {
fragment.appendChild(document.createTextNode(tokens[i]));
}
else {
const get_value = create_getter(evaluateLater, node.parentNode, tokens[i]);
const text = document.createTextNode("");
fragment.append(text);
update(() => text.textContent = get_value());
}
}
mutateDom(() =>
node.parentElement.replaceChild(fragment, node));
}
}
function process_attributes(node) {
for (let attr of node.attributes) {
const matches = [...attr.value.matchAll(placeholder_regex)];
if (matches.length) {
const template = attr.value;
update(() => attr.value = template.replace(placeholder_regex, (_, expr) => create_getter(evaluateLater, node, expr)()));
}
}
}
function process_nodes(node) {
for (let child of node.childNodes) {
process(child);
}
}
});
}
function anchor_block(el, template, { addScopeToNode, cleanup, initTree, mutateDom, scope = {} }) {
if (el._r_block) {
return;
}
initialize();
let nodes = is_template(template)
? [...template.content.cloneNode(true).childNodes]
: [template.cloneNode(true)];
mutateDom(() => {
for (let node of nodes) {
is_element(node) && addScopeToNode(node, scope, el);
el.parentElement.insertBefore(node, el);
is_element(node) && initTree(node);
}
});
el._r_block = {
template,
update() {
mutateDom(() => {
for (let node of nodes ?? []) {
el.parentElement.insertBefore(node, el);
}
});
},
delete() {
el._r_block = null;
for (let node of nodes ?? []) {
node.remove();
}
nodes = null;
}
};
cleanup(() => el._r_block?.delete());
}
function initialize() {
document.body._r_block ??= (() => {
const observer = new MutationObserver(mutations => {
for (let mutation of mutations) {
for (let node of mutation.addedNodes) {
node._r_block?.update();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
return observer;
})();
}
function plugin$3({ addScopeToNode, directive, initTree, mutateDom }) {
directive("fragment", (el, {}, { cleanup }) => {
if (!is_template(el)) {
warn("x-fragment can only be used on a 'template' tag");
return;
}
anchor_block(el, el, { addScopeToNode, cleanup, initTree, mutateDom });
});
}
function plugin$2({ addScopeToNode, directive, initTree, mutateDom }) {
directive("match", (el, { }, { cleanup, effect, evaluateLater }) => {
if (!is_template(el)) {
warn("x-match can only be used on a 'template' tag");
return;
}
const branches = [];
const has_default_case = () => branches.some(b => b.default);
for (let node of el.content.children) {
const expr = node.getAttribute("x-case");
if (expr !== null) {
has_default_case() && warn("The x-case directive cannot be appear after x-default");
branches.push({ el: node, get_value: create_getter(evaluateLater, expr) });
}
else if (node.hasAttribute("x-default")) {
has_default_case() && warn("Only one x-default directive is allowed");
branches.push({ el: node, get_value: () => true, default: true });
}
else {
warn("Element has no x-case or x-default directive and will be ignored", node);
}
}
const activate = branch => {
if (el._r_block?.template !== branch.el) {
clear();
anchor_block(el, branch.el, {
addScopeToNode,
cleanup,
initTree,
mutateDom
});
}
};
const clear = () => el._r_block?.delete();
effect(() => {
let active;
for (let branch of branches) {
if (branch.get_value() && !active) {
active = branch;
}
}
active ? activate(active) : clear();
});
});
}
function plugin$1(alpine) {
alpine.directive("template", (el, { expression }) => {
if (is_template(el)) {
warn("x-template cannot be used on a 'template' tag");
return;
}
const tpl = document.getElementById(expression);
if (!is_template(tpl)) {
warn("x-template directive can only reference the template tag");
return;
}
queueMicrotask(() => {
el.innerHTML = "";
el.append(tpl.content.cloneNode(true));
});
});
}
function plugin({ addScopeToNode, directive, initTree, mutateDom }) {
directive("when", (el, { expression }, { cleanup, effect, evaluateLater }) => {
if (!is_template(el)) {
warn("x-when can only be used on a 'template' tag");
return;
}
const activate = () => anchor_block(el, el, { addScopeToNode, cleanup, initTree, mutateDom });
const clear = () => el._r_block?.delete();
const get = create_getter(evaluateLater, expression);
effect(() => get() ? activate() : clear());
});
}
function __main(alpine) {
plugin$5(alpine);
plugin$4(alpine);
plugin$3(alpine);
plugin$2(alpine);
plugin$1(alpine);
plugin(alpine);
}
document.addEventListener("alpine:init", () => { Alpine.plugin(__main); });
})();