release: WRNexusJS 0.3.5

This commit is contained in:
2026-07-24 12:46:44 +05:30
parent 44ba847210
commit c81dedff17
2114 changed files with 65790 additions and 150559 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/csr",
"version": "0.3.4",
"version": "0.3.5",
"type": "module",
"main": "src/index.ts",
"exports": {
+654 -5
View File
@@ -618,15 +618,34 @@ export const REACTIVE_RUNTIME = String.raw`
}
var behaviorFunctions = {};
var componentEventTarget = el.querySelector("[data-wrn-events]") || el;
var declaredEvents = new Set(
String(componentEventTarget.getAttribute("data-wrn-events") || "")
.split(",")
.map(function (name) { return name.trim(); })
.filter(Boolean),
);
var stateWatchers = {};
var anyStateListeners = new Set();
var cleanupCallbacks = [];
var disposed = false;
function readGlobal(name) {
if (declaredEvents.has(name)) {
return function (detail) {
return dispatchComponentEvent(componentEventTarget, name, detail);
};
}
if (name === "$emit") {
return function (eventName, detail) {
return dispatchComponentEvent(componentEventTarget, eventName, detail);
};
}
if (name === "window") return window;
if (name === "document") return document;
if (name === "console") return console;
if (name === "Array") return Array;
if (name === "Number") return Number;
if (name === "Math") return Math;
if (name === "JSON") return JSON;
if (name === "Date") return Date;
@@ -1489,6 +1508,10 @@ export const REACTIVE_RUNTIME = String.raw`
// Keep visible when evaluation is unavailable.
}
node.setAttribute(
"data-show",
visible ? "true" : "false",
);
node.style.display = visible ? "" : "none";
});
});
@@ -1718,6 +1741,19 @@ export const REACTIVE_RUNTIME = String.raw`
if (behavior) {
installBehaviorFunctions(behavior.functions);
var publicScopeApi = {
get: peekScope,
set: writeScope,
call: function (name) {
var fn = behaviorFunctions[name];
if (typeof fn !== "function") return undefined;
return fn.apply(null, Array.prototype.slice.call(arguments, 1));
},
};
el.__wrnexusScopeApi = publicScopeApi;
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
select.__wrnexusScopeApi = publicScopeApi;
});
(behavior.effects || []).forEach(function (source) {
if (typeof source !== "string" || !source.trim()) return;
@@ -1774,6 +1810,10 @@ export const REACTIVE_RUNTIME = String.raw`
}
mountedBehaviorRoots.delete(el);
behaviorInstances.delete(el);
delete el.__wrnexusScopeApi;
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
delete select.__wrnexusScopeApi;
});
el.__wrnexusScope = false;
},
};
@@ -1909,6 +1949,602 @@ export const REACTIVE_RUNTIME = String.raw`
}
ensureBehaviorObserver();
hydrateSelectControllers(host);
hydratePinInputControllers(host);
}
var selectOutsideClickBound = false;
function selectScopeApi(root) {
var scope = root;
while (scope) {
if (scope.__wrnexusScopeApi) return scope.__wrnexusScopeApi;
scope = scope.parentElement;
}
return null;
}
function selectPayloadOptions(payload) {
if (Array.isArray(payload)) return payload;
if (!payload || typeof payload !== "object") return [];
if (Array.isArray(payload.options)) return payload.options;
if (Array.isArray(payload.items)) return payload.items;
if (Array.isArray(payload.results)) return payload.results;
if (Array.isArray(payload.data)) return payload.data;
return [];
}
function syncComboboxInput(root) {
if (!root || !root.hasAttribute("data-wrn-combobox")) return;
var input = root.querySelector(".wire-next__combobox-input");
var api = selectScopeApi(root);
if (!input || !api) return;
var value = api.call("inputText") || "";
input.value = String(value);
input.setAttribute("value", String(value));
}
function selectEventDetail(root, extra) {
var api = selectScopeApi(root);
var multiple = !!(api && api.get("multiple"));
var detail = {
component: root.hasAttribute("data-wrn-combobox") ? "ComboBox" : "AdvancedSelect",
query: api ? (api.get("query") || "") : "",
value: api ? (api.get("selectedValue") || "") : "",
values: api && Array.isArray(api.get("selectedValues")) ? api.get("selectedValues") : [],
multiple: multiple,
selectedOptions: api ? (api.call("selectedOptions") || []) : [],
};
if (extra && typeof extra === "object") {
Object.keys(extra).forEach(function (key) { detail[key] = extra[key]; });
}
return detail;
}
function emitSelectEvent(root, name, extra) {
try {
dispatchComponentEvent(root, name, selectEventDetail(root, extra));
} catch (_) {
// CustomEvent can be unavailable in minimal DOM environments.
}
}
function renderRemoteSelectOptions(root, options) {
var list = root.querySelector(".wire-next__select-list");
if (!list) return;
list.querySelectorAll(".wire-next__option-group, .wire-next__select-option, .wire-next__select-message")
.forEach(function (node) { node.remove(); });
options.forEach(function (option) {
if (!option || option.value == null) return;
var button = document.createElement("button");
button.type = "button";
button.className = "wire-next__select-option";
button.setAttribute("role", "option");
button.setAttribute("data-option-value", String(option.value));
button.disabled = !!option.disabled;
if (option.icon) {
var icon = document.createElement("i");
icon.className = String(option.icon);
icon.setAttribute("aria-hidden", "true");
button.appendChild(icon);
} else if (option.avatar) {
var avatar = document.createElement("img");
avatar.src = String(option.avatar);
avatar.alt = "";
button.appendChild(avatar);
} else if (option.color) {
var dot = document.createElement("i");
dot.className = "wire-next__color-dot";
dot.style.setProperty("--option-color", String(option.color));
button.appendChild(dot);
}
var copy = document.createElement("span");
var label = document.createElement("strong");
label.textContent = String(option.label == null ? option.value : option.label);
copy.appendChild(label);
if (option.description) {
var description = document.createElement("small");
description.textContent = String(option.description);
copy.appendChild(description);
}
button.appendChild(copy);
var check = document.createElement("i");
check.className = "wire-next__check icon-[lucide--check]";
check.setAttribute("aria-hidden", "true");
check.style.display = "none";
button.appendChild(check);
button.addEventListener("click", function () {
var api = selectScopeApi(root);
if (!api || button.disabled) return;
api.call("chooseOption", option);
window.setTimeout(function () {
var selected = api.call("isSelected", option);
button.classList.toggle("wire-next__select-option--selected", !!selected);
button.setAttribute("aria-selected", selected ? "true" : "false");
check.style.display = selected ? "" : "none";
syncComboboxInput(root);
}, 0);
});
list.appendChild(button);
});
}
function setupSelectController(root) {
if (!root || root.__wrnexusSelectController) return;
root.__wrnexusSelectController = { attempts: 0 };
var remote = root.getAttribute("data-remote") === "true";
var remoteUrl = root.getAttribute("data-remote-url") || "";
var input = root.querySelector(".wire-next__select-search input, .wire-next__combobox-input");
var list = root.querySelector(".wire-next__select-list");
var loadMore = root.querySelector("[data-wrn-select-load-more]");
var timer = 0;
var requestId = 0;
var loadedOptions = [];
var retainedSelections = [];
var currentPage = Number(root.getAttribute("data-page") || 1);
function setLoading(loading) {
root.setAttribute("data-loading", loading ? "true" : "false");
if (list) list.setAttribute("aria-busy", loading ? "true" : "false");
var api = selectScopeApi(root);
if (api) api.set("loading", loading);
}
function load(append, allowEmptyQuery) {
root.__wrnexusSelectController.attempts++;
if (!remote || !remoteUrl) return;
var api = selectScopeApi(root);
if (!api) return;
var query = input ? input.value : "";
var minimum = Number(api.get("minSearchLength") || 0);
if (!allowEmptyQuery && query.length < minimum) return;
var url;
try {
url = new URL(remoteUrl, window.location.href);
} catch (_) {
return;
}
var parameter = root.getAttribute("data-remote-query-param") || "q";
if (query) url.searchParams.set(parameter, query);
var nextPage = append ? currentPage + 1 : 1;
url.searchParams.set("page", String(nextPage));
var thisRequest = ++requestId;
setLoading(true);
fetch(url.toString(), { headers: { accept: "application/json" } })
.then(function (response) {
if (!response.ok) throw new Error("HTTP " + response.status);
return response.json();
})
.then(function (payload) {
if (thisRequest !== requestId) return;
var nextOptions = selectPayloadOptions(payload);
loadedOptions = append ? loadedOptions.concat(nextOptions) : nextOptions;
currentPage = nextPage;
var selectedValues = api.get("selectedValues");
var selectedValue = api.get("selectedValue");
var selectedKeys = api.get("multiple")
? (Array.isArray(selectedValues) ? selectedValues : [])
: (selectedValue ? [selectedValue] : []);
var previousOptions = api.call("allOptions") || [];
retainedSelections = retainedSelections
.concat(previousOptions)
.concat(loadedOptions)
.filter(function (option, index, list) {
return option && selectedKeys.includes(option.value) &&
list.findIndex(function (item) { return item && item.value === option.value; }) === index;
});
var stateOptions = retainedSelections.concat(loadedOptions).filter(function (option, index, list) {
return option && list.findIndex(function (item) { return item && item.value === option.value; }) === index;
});
api.set("options", stateOptions);
api.set("groups", []);
api.set("page", currentPage);
api.set("hasMore", !!(payload && (payload.hasMore || payload.nextPage || payload.next)));
renderRemoteSelectOptions(root, loadedOptions);
emitSelectEvent(root, "load", {
options: nextOptions,
page: currentPage,
hasMore: !!(payload && (payload.hasMore || payload.nextPage || payload.next)),
append: !!append,
url: url.toString(),
payload: payload,
});
})
.catch(function (error) {
console.error("[wrnexus] advanced select remote request failed", error);
emitSelectEvent(root, "error", {
error: error,
message: error && error.message ? error.message : String(error),
url: url.toString(),
});
})
.then(function () {
if (thisRequest === requestId) setLoading(false);
});
}
if (input) {
input.addEventListener("input", function () {
emitSelectEvent(root, "search", { query: input.value });
if (!remote) return;
window.clearTimeout(timer);
var debounce = Number(root.getAttribute("data-remote-debounce") || 250);
timer = window.setTimeout(function () { load(false); }, debounce);
});
}
if (loadMore) {
loadMore.addEventListener("click", function () { load(true); });
}
if (list && root.getAttribute("data-infinite") === "true") {
list.addEventListener("scroll", function () {
if (list.scrollTop + list.clientHeight < list.scrollHeight - 24) return;
var api = selectScopeApi(root);
if (api && api.get("hasMore") && root.getAttribute("data-loading") !== "true") load(true);
});
}
root.addEventListener("click", function (event) {
if (!remote || !remoteUrl) return;
if (!event.target.closest(".wire-next__select-trigger")) return;
if (loadedOptions.length === 0) load(false);
});
root.__wrnexusSelectController.load = load;
var dropdown = root.querySelector(".wire-next__select-dropdown");
var lastOpen = !!(selectScopeApi(root) && selectScopeApi(root).get("open"));
function emitOpenStateIfChanged() {
var api = selectScopeApi(root);
var nextOpen = !!(api && api.get("open"));
if (nextOpen === lastOpen) return;
lastOpen = nextOpen;
emitSelectEvent(root, nextOpen ? "open" : "close");
}
var ObserverConstructor =
root.ownerDocument &&
root.ownerDocument.defaultView &&
root.ownerDocument.defaultView.MutationObserver
? root.ownerDocument.defaultView.MutationObserver
: (typeof MutationObserver !== "undefined" ? MutationObserver : null);
if (ObserverConstructor) {
new ObserverConstructor(function () {
emitOpenStateIfChanged();
}).observe(dropdown || root, {
attributes: true,
attributeFilter: dropdown ? ["data-show", "style"] : ["class"],
});
}
root.addEventListener("click", function (event) {
var optionButton = event.target.closest(".wire-next__select-option");
if (optionButton) {
window.setTimeout(function () {
var api = selectScopeApi(root);
var optionValue = optionButton.getAttribute("data-option-value");
var options = api ? (api.call("allOptions") || []) : [];
var option = options.find(function (item) {
return item && String(item.value) === String(optionValue);
});
emitSelectEvent(root, "select", { option: option || null });
emitSelectEvent(root, "change", { option: option || null, reason: "select" });
emitOpenStateIfChanged();
}, 0);
}
if (event.target.closest(".wire-next__clear-select")) {
window.setTimeout(function () {
emitSelectEvent(root, "clear");
emitSelectEvent(root, "change", { reason: "clear" });
}, 0);
}
}, true);
root.addEventListener("keydown", function (event) {
if (event.key !== "Enter") return;
var before = selectEventDetail(root);
window.setTimeout(function () {
var after = selectEventDetail(root);
var beforeSelection = before.multiple ? JSON.stringify(before.values) : before.value;
var afterSelection = after.multiple ? JSON.stringify(after.values) : after.value;
if (afterSelection === beforeSelection) return;
syncComboboxInput(root);
emitSelectEvent(root, "select", { source: "keyboard" });
emitSelectEvent(root, "change", { reason: "select", source: "keyboard" });
emitOpenStateIfChanged();
}, 0);
}, true);
if (root.hasAttribute("data-wrn-combobox")) {
root.wrnexusCombobox = {
open: function () {
var api = selectScopeApi(root);
if (api) api.call("openDropdown");
emitOpenStateIfChanged();
},
close: function () {
var api = selectScopeApi(root);
if (api) api.call("closeDropdown");
emitOpenStateIfChanged();
},
clear: function () {
var api = selectScopeApi(root);
if (api) api.call("clearValue");
syncComboboxInput(root);
emitSelectEvent(root, "clear", { source: "method" });
emitSelectEvent(root, "change", { reason: "clear", source: "method" });
},
setValue: function (value) {
var api = selectScopeApi(root);
if (api) api.call("setValue", value);
syncComboboxInput(root);
emitSelectEvent(root, "change", { reason: "setValue", source: "method" });
},
reload: function () {
load(false, true);
},
};
root.addEventListener("click", function (event) {
if (!event.target.closest(".wire-next__select-option, .wire-next__clear-select")) return;
window.setTimeout(function () { syncComboboxInput(root); }, 0);
});
if (input) {
input.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
window.setTimeout(function () { syncComboboxInput(root); }, 0);
}
});
}
}
if (remote && remoteUrl && root.getAttribute("data-remote-auto-load") !== "false") {
load(false, true);
}
}
function hydrateSelectControllers(root) {
var host = root || document;
var selects = [];
if (host.nodeType === 1 && host.matches && host.matches("[data-wrn-select]")) selects.push(host);
if (host.querySelectorAll) {
Array.prototype.push.apply(selects, host.querySelectorAll("[data-wrn-select]"));
}
selects.forEach(setupSelectController);
if (!selectOutsideClickBound) {
selectOutsideClickBound = true;
document.addEventListener("pointerdown", function (event) {
document.querySelectorAll("[data-wrn-select].wire-next--open").forEach(function (select) {
if (select.contains(event.target)) return;
var api = selectScopeApi(select);
if (api) api.set("open", false);
});
});
}
}
function dispatchComponentEvent(root, name, detail) {
if (!root || !name) return null;
var EventConstructor =
root.ownerDocument &&
root.ownerDocument.defaultView &&
root.ownerDocument.defaultView.CustomEvent
? root.ownerDocument.defaultView.CustomEvent
: CustomEvent;
var event = new EventConstructor(String(name), {
bubbles: true,
detail: detail || {},
});
root.dispatchEvent(event);
// Compatibility for applications using the former prefixed contract.
root.dispatchEvent(new EventConstructor("wrnexus:" + String(name), {
bubbles: true,
detail: detail || {},
}));
return event;
}
function emitPinInputEvent(root, name, extra) {
var hidden = root.querySelector("[data-pin-value]");
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
var value = hidden ? hidden.value : "";
var detail = {
component: "PinInput",
value: value,
digits: cells.map(function (cell) { return cell.value; }),
complete: value.length === cells.length,
length: cells.length,
};
if (extra && typeof extra === "object") {
Object.keys(extra).forEach(function (key) { detail[key] = extra[key]; });
}
try {
dispatchComponentEvent(root, name, detail);
} catch (_) {
// CustomEvent can be unavailable in minimal DOM environments.
}
}
function setupPinInputController(root) {
if (!root || root.__wrnexusPinInputController) return;
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
var hidden = root.querySelector("[data-pin-value]");
var clearButton = root.querySelector("[data-pin-clear]");
var patternSource = root.getAttribute("data-pattern") || "[0-9]";
var allowPaste = root.getAttribute("data-allow-paste") !== "false";
var autoSubmit = root.getAttribute("data-auto-submit") === "true";
var matcher;
var exactMatcher;
var NativeEventConstructor =
root.ownerDocument &&
root.ownerDocument.defaultView &&
root.ownerDocument.defaultView.Event
? root.ownerDocument.defaultView.Event
: Event;
try {
exactMatcher = new RegExp("^(?:" + patternSource + ")$");
matcher = new RegExp("^(?:" + patternSource + ")$", "i");
} catch (error) {
matcher = /$a/;
emitPinInputEvent(root, "error", {
error: error,
message: "Invalid PIN pattern: " + patternSource,
});
}
function acceptedCharacters(value) {
return Array.from(String(value || "")).map(function (character) {
matcher.lastIndex = 0;
if (!matcher.test(character)) return "";
exactMatcher.lastIndex = 0;
if (exactMatcher.test(character)) return character;
var upper = character.toUpperCase();
exactMatcher.lastIndex = 0;
if (exactMatcher.test(upper)) return upper;
var lower = character.toLowerCase();
exactMatcher.lastIndex = 0;
return exactMatcher.test(lower) ? lower : character;
}).filter(Boolean);
}
function currentValue() {
return cells.map(function (cell) { return cell.value; }).join("");
}
function updateClearButton(value) {
if (!clearButton) return;
clearButton.style.display = value ? "" : "none";
}
function syncValue(reason, index) {
var previous = hidden ? hidden.value : "";
var value = currentValue();
if (hidden) {
hidden.value = value;
hidden.setAttribute("value", value);
hidden.dispatchEvent(new NativeEventConstructor("input", { bubbles: true }));
hidden.dispatchEvent(new NativeEventConstructor("change", { bubbles: true }));
}
root.setAttribute("data-value", value);
root.setAttribute("data-complete", value.length === cells.length ? "true" : "false");
updateClearButton(value);
emitPinInputEvent(root, "input", { reason: reason, index: index });
if (value !== previous) {
emitPinInputEvent(root, "change", { reason: reason, index: index });
}
if (value.length === cells.length && previous.length !== cells.length) {
emitPinInputEvent(root, "complete", { reason: reason, index: index });
if (autoSubmit) {
var form = root.closest("form");
if (form && typeof form.requestSubmit === "function") form.requestSubmit();
}
}
}
function setValue(value, reason) {
var characters = acceptedCharacters(value).slice(0, cells.length);
cells.forEach(function (cell, index) {
cell.value = characters[index] || "";
cell.setAttribute("value", cell.value);
});
syncValue(reason || "setValue", characters.length ? characters.length - 1 : -1);
}
function focusCell(index) {
var cell = cells[Math.max(0, Math.min(cells.length - 1, index))];
if (cell && !cell.disabled) {
cell.focus();
if (typeof cell.select === "function") cell.select();
}
}
cells.forEach(function (cell, index) {
cell.addEventListener("focus", function () {
if (typeof cell.select === "function") cell.select();
});
cell.addEventListener("input", function () {
var characters = acceptedCharacters(cell.value);
cell.value = characters.length ? characters[characters.length - 1] : "";
cell.setAttribute("value", cell.value);
syncValue("input", index);
if (cell.value && index < cells.length - 1) focusCell(index + 1);
});
cell.addEventListener("keydown", function (event) {
if (event.key === "Backspace" && !cell.value && index > 0) {
event.preventDefault();
cells[index - 1].value = "";
cells[index - 1].setAttribute("value", "");
syncValue("backspace", index - 1);
focusCell(index - 1);
} else if (event.key === "ArrowLeft" && index > 0) {
event.preventDefault();
focusCell(index - 1);
} else if (event.key === "ArrowRight" && index < cells.length - 1) {
event.preventDefault();
focusCell(index + 1);
} else if (event.key === "Home") {
event.preventDefault();
focusCell(0);
} else if (event.key === "End") {
event.preventDefault();
focusCell(cells.length - 1);
}
});
cell.addEventListener("paste", function (event) {
if (!allowPaste) return;
var clipboard = event.clipboardData;
if (!clipboard) return;
event.preventDefault();
var characters = acceptedCharacters(clipboard.getData("text")).slice(0, cells.length - index);
characters.forEach(function (character, offset) {
cells[index + offset].value = character;
cells[index + offset].setAttribute("value", character);
});
syncValue("paste", index);
emitPinInputEvent(root, "paste", { index: index, pasted: characters.join("") });
focusCell(Math.min(index + characters.length, cells.length - 1));
});
});
if (clearButton) {
clearButton.addEventListener("click", function () {
setValue("", "clear");
emitPinInputEvent(root, "clear");
focusCell(0);
});
}
root.wrnexusPinInput = {
focus: function (index) { focusCell(Number(index || 0)); },
clear: function () {
setValue("", "clear");
emitPinInputEvent(root, "clear", { source: "method" });
},
setValue: function (value) { setValue(value, "setValue"); },
getValue: function () { return currentValue(); },
};
root.__wrnexusPinInputController = root.wrnexusPinInput;
setValue(hidden ? hidden.value : root.getAttribute("data-value"), "initial");
}
function hydratePinInputControllers(root) {
var host = root || document;
var inputs = [];
if (host.nodeType === 1 && host.matches && host.matches("[data-wrn-pin-input]")) {
inputs.push(host);
}
if (host.querySelectorAll) {
Array.prototype.push.apply(inputs, host.querySelectorAll("[data-wrn-pin-input]"));
}
inputs.forEach(setupPinInputController);
}
function parseScopeDecl(decl) {
@@ -2392,7 +3028,7 @@ export const REACTIVE_RUNTIME = String.raw`
* trying to parse the callback in the same regex.
*/
var methodMatch =
/^([\s\S]+)\.(filter|map|some|every|find|findIndex)\(\s*([\s\S]*)\s*\)$/.exec(
/^([\s\S]+)\.(filter|map|flatMap|some|every|find|findIndex)\(\s*([\s\S]*)\s*\)$/.exec(
source,
);
@@ -2503,6 +3139,11 @@ export const REACTIVE_RUNTIME = String.raw`
) {
result =
collection.map(callback);
} else if (
method === "flatMap"
) {
result =
collection.flatMap(callback);
} else if (
method === "some"
) {
@@ -2561,11 +3202,13 @@ export const REACTIVE_RUNTIME = String.raw`
function next() { return tokens[index++]; }
function is(v) { return peek() && peek().value === v; }
function match(v) { if (is(v)) { index++; return true; } return false; }
function expect(v) { if (!match(v)) throw new Error("Expected '" + v + "'"); }
function expect(v) {
if (!match(v)) throw new Error("Expected '" + v + "' in '" + expr + "'");
}
function parsePrimary() {
var t = next();
if (!t) throw new Error("Unexpected end of expression");
if (!t) throw new Error("Unexpected end of expression in '" + expr + "'");
if (t.type === "number" || t.type === "string") return { value: t.value };
if (t.type === "ident") {
if (t.value === "true") return { value: true };
@@ -2659,12 +3302,18 @@ export const REACTIVE_RUNTIME = String.raw`
}
function parseAnd() {
var l = parseEq();
while (match("&&")) l = l && parseEq();
while (match("&&")) {
var r = parseEq();
l = l && r;
}
return l;
}
function parseOr() {
var l = parseAnd();
while (match("||")) l = l || parseAnd();
while (match("||")) {
var r = parseAnd();
l = l || r;
}
return l;
}
function parseTernary() {
+41
View File
@@ -41,6 +41,23 @@ test("@event (data-on-click) mutates a signal and re-renders", () => {
expect(btn.textContent).toBe("2");
});
test("declared component events emit through the generic $emit function", () => {
const win = mount(
`<div data-scope="" data-wrn-events="complete">
<button data-on-click="$emit('complete', { value: '4829' })">Complete</button>
</div>`,
);
const root = win.document.querySelector("[data-wrn-events]")!;
let detail: Record<string, unknown> | undefined;
root.addEventListener("complete", (event) => {
detail = (event as unknown as CustomEvent).detail;
});
win.document.querySelector("button")!.click();
expect(detail).toEqual({ value: "4829" });
});
test("nested scopes don't clobber each other (regression)", () => {
// An empty outer scope must not touch inner scopes' values.
const win = mount(
@@ -131,6 +148,26 @@ test("expression evaluator: member access, ternary, comparison, calls", () => {
expect(win.document.getElementById("c")!.textContent).toBe("3");
});
test("logical expressions consume their right-hand side when the result short-circuits", () => {
const win = mount(
`<div data-scope="leftFalse: false, leftTrue: true">
<span id="and" data-text="leftFalse && 123"></span>
<span id="or" data-text="leftTrue || 456"></span>
</div>`,
);
expect(win.document.getElementById("and")!.textContent).toBe("false");
expect(win.document.getElementById("or")!.textContent).toBe("true");
});
test("expression evaluator supports flatMap callbacks", () => {
const win = mount(
`<div data-scope="groups: [{items: [1, 2]}, {items: [3]}]">
<span data-text="groups.flatMap((group) => group.items)"></span>
</div>`,
);
expect(win.document.querySelector("span")!.textContent).toBe("1,2,3");
});
test("data-show toggles visibility on a reactive expression (tabs pattern)", () => {
const win = mount(
`<div data-scope="tab: 0">
@@ -143,9 +180,13 @@ test("data-show toggles visibility on a reactive expression (tabs pattern)", ()
(win.document.getElementById(id) as unknown as HTMLElement).style.display;
expect(disp("a")).toBe("");
expect(disp("b")).toBe("none");
expect(win.document.getElementById("a")!.getAttribute("data-show")).toBe("true");
expect(win.document.getElementById("b")!.getAttribute("data-show")).toBe("false");
(win.document.querySelector("button") as unknown as HTMLElement).click();
expect(disp("a")).toBe("none");
expect(disp("b")).toBe("");
expect(win.document.getElementById("a")!.getAttribute("data-show")).toBe("false");
expect(win.document.getElementById("b")!.getAttribute("data-show")).toBe("true");
});
test("reactive attribute bindings update input and accessibility attributes", () => {