release: WRNexusJS 0.3.5
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user