i?i:l};t.addEventListener("mousedown",e=>{const i=t.isVertical?a.offsetHeight:a.offsetWidth;f.start(e,{onmousemove:({w:e,h:n})=>{const l=s(i,e,n);a.style[t.isVertical?"height":"width"]=l+"px",t.dispatchEvent(new CustomEvent("resizing",{detail:{oldSize:i,newSize:l},bubbles:!1}))},onmouseup:({w:e,h:a})=>{const n=s(i,e,a);t.dispatchEvent(new CustomEvent("resize",{detail:{oldSize:i,newSize:n},bubbles:!1})),t.dispatchEvent(new CustomEvent("change",{detail:n,bubbles:!1}))}})})},i.makeDom("\n\n")),"undefined"!=typeof window&&window.addEventListener("beforeunload",t=>{s.exitBlocks>0&&t.preventDefault()});const v=document.documentElement;v.hasAttribute("$data-bs-theme")||v.hasAttribute("data-bs-theme")||v.setAttribute("$data-bs-theme","LocalStorage.darkMode?'dark':'light'"),globalThis.HTTP=o,globalThis.UI=d,globalThis.AutoForm=m,globalThis.MouseMover=f,globalThis.VirtualScroll=p;const y={HTTP:o,UI:d,AutoForm:m,MouseMover:f,VirtualScroll:p,State:s,List:p};"undefined"!=typeof document&&(globalThis.ApigoBase=y);export{r as APIComponent,m as AutoForm,o as HTTP,f as MouseMover,l as State,d as UI,p as VirtualScroll};
diff --git a/dist/base.mjs b/dist/base.mjs
deleted file mode 100644
index 0461377..0000000
--- a/dist/base.mjs
+++ /dev/null
@@ -1,961 +0,0 @@
-import { Component, NewState, Util, $, State, Hash } from "@apigo.cc/state";
-import { State as State2 } from "@apigo.cc/state";
-import "@apigo.cc/bootstrap";
-const HTTP = {
- get: ({ url, ...opt }) => HTTP.request({ url, method: "GET", ...opt }),
- post: ({ url, data, ...opt }) => HTTP.request({ url, method: "POST", data, ...opt }),
- put: ({ url, data, ...opt }) => HTTP.request({ url, method: "PUT", data, ...opt }),
- delete: ({ url, ...opt }) => HTTP.request({ url, method: "DELETE", ...opt }),
- head: ({ url, ...opt }) => HTTP.request({ url, method: "HEAD", ...opt }),
- request: async ({ url, method = "POST", data = void 0, headers = {}, responseType, timeout = 1e4 }) => {
- var _a;
- method = method.toUpperCase();
- const options = { method, signal: (_a = AbortSignal.timeout) == null ? void 0 : _a.call(AbortSignal, timeout) };
- if (data !== void 0 && method !== "GET" && method !== "HEAD") {
- if (data instanceof HTMLFormElement) data = new FormData(data);
- if (data && typeof data === "object" && !(data instanceof FormData) && !(data instanceof ArrayBuffer || ArrayBuffer.isView(data)) && Object.values(data).some((v) => v instanceof File || v instanceof Blob || v instanceof FileList || Array.isArray(v) && v.some((i) => i instanceof File || i instanceof Blob))) {
- const fd = new FormData();
- for (const [k, v] of Object.entries(data)) {
- if (v instanceof FileList || Array.isArray(v)) Array.from(v).forEach((item) => fd.append(k, item));
- else if (v !== void 0 && v !== null) fd.append(k, v);
- }
- data = fd;
- }
- if (data instanceof FormData) {
- delete headers["Content-Type"];
- } else if (typeof data !== "string" && !(data instanceof ArrayBuffer || ArrayBuffer.isView(data))) {
- data = JSON.stringify(data);
- if (!headers["Content-Type"]) headers["Content-Type"] = "application/json";
- }
- options.body = data;
- }
- if (Object.keys(headers).length) options.headers = headers;
- const response = { error: null, ok: null, status: 0, headers: {}, responseType: "", result: null };
- try {
- const resp = await fetch(url, options);
- Object.assign(response, { ok: resp.ok, status: resp.status, headers: Object.fromEntries(resp.headers.entries()) });
- if (!responseType) {
- const contentType = resp.headers.get("Content-Type") || "";
- if (contentType.includes("application/json")) responseType = "json";
- else if (/image|video|audio|pdf|zip|octet-stream/.test(contentType)) responseType = "binary";
- else responseType = "text";
- response.responseType = responseType;
- }
- if (response.ok === false) response.error = (response.statusText || "HTTP " + response.status + " error") + " for " + url;
- if (responseType === "json") response.result = await resp.json();
- else response.result = responseType === "binary" ? await resp.arrayBuffer() : await resp.text();
- } catch (err) {
- Object.assign(response, { error: err.message || String(err), ok: false });
- }
- return response;
- }
-};
-const APIComponent = Component.register("API", (container) => {
- container.request = NewState({ url: "", method: "GET", headers: {}, data: null, timeout: 1e4, responseType: "" });
- container.response = NewState({ loading: false, ok: null, status: null, error: null, headers: {}, responseType: "", result: null });
- container.result = NewState();
- container.do = (opt = {}) => {
- return new Promise((resolve, reject) => {
- const req = { ...container.request, ...opt };
- if (!req.url) throw new Error(".url is required");
- req.headers = { ...container.request.headers, ...opt.headers };
- container.response.loading = true;
- HTTP.request(req).then((resp) => {
- Object.keys(resp).forEach((k) => {
- if (k !== "result") container.response[k] = resp[k];
- });
- if (resp.result && typeof resp.result === "object" && container.result && typeof container.result === "object") {
- Object.assign(container.result, resp.result);
- } else {
- container.result = resp.result;
- }
- container.response.loading = false;
- if (resp.ok === false) throw new Error(resp.error);
- if (typeof resp.result === "object" && resp.result.error) throw new Error(resp.result.error);
- container.dispatchEvent(new CustomEvent("response", { detail: resp, bubbles: false }));
- resolve(resp);
- }).catch((err) => {
- var _a;
- if (!opt.noui && ((_a = globalThis.UI) == null ? void 0 : _a.toast)) UI.toast(err.message, { type: "danger" });
- container.dispatchEvent(new CustomEvent("error", { detail: err, bubbles: true }));
- reject(err);
- });
- });
- };
- let _autoTimer = null;
- container.request.__watch(null, () => {
- if (!container.hasAttribute("auto") || !container.request.url) return;
- if (_autoTimer) return;
- _autoTimer = Promise.resolve().then(() => {
- container.do();
- _autoTimer = null;
- });
- });
-});
-const UI$1 = {};
-Component.register("Modal", (container) => {
- container.modal = new bootstrap.Modal(container);
- container.addEventListener("bind", (e) => {
- e.detail ? container.modal.show() : container.modal.hide();
- });
- container.addEventListener("hide.bs.modal", () => {
- var _a;
- (_a = document.activeElement) == null ? void 0 : _a.blur();
- container.dispatchEvent(new CustomEvent("change", { bubbles: false, detail: false }));
- });
- Util.copyFunction(container, container.modal, "show", "hide");
-}, Util.makeDom(
- /*html*/
- `
-
-`
-));
-Component.register("Dialog", Component.getSetupFunction("Modal"), Util.makeDom(
- /*html*/
- `
-
-`
-));
-let _dialogCount = 0;
-UI$1.showDialog = function({ title = "", message = "", buttons = ["{#Close#}"], type = "body" }) {
- const d = document.body.appendChild(document.createElement("Dialog"));
- d.style.zIndex = 2e3 + ++_dialogCount;
- Promise.resolve().then(() => {
- Object.assign(d.state, { message, title, type, buttons });
- d.show();
- });
- return new Promise((resolve) => {
- d.addEventListener("change", (e) => {
- _dialogCount--;
- resolve(d.result || 0);
- d.remove();
- });
- });
-};
-UI$1.alert = function(message, options = {}) {
- return UI$1.showDialog({ message, ...options });
-};
-UI$1.confirm = function(message, options = {}) {
- return new Promise((resolve) => UI$1.showDialog({ message, buttons: ["{#Cancel#}", "{#Confirm#}"], ...options }).then((index2) => resolve(index2 >= 2)).catch(() => resolve(false)));
-};
-Component.register("Toast", (container) => {
- container.toast = new bootstrap.Toast(container, { autohide: container.state.delay > 0 });
- Util.copyFunction(container, container.toast, "show", "hide");
- container.addEventListener("show.bs.toast", () => {
- if (container.state.delay > 0) {
- let timer;
- const startTimer = () => {
- container.state.left = container.state.delay / 1e3;
- timer = setInterval(() => {
- if (!container.isConnected || --container.state.left <= 0) clearInterval(timer);
- }, 1e3);
- };
- startTimer();
- container.addEventListener("mouseenter", () => {
- clearInterval(timer);
- container.state.left = void 0;
- });
- container.addEventListener("mouseleave", startTimer);
- }
- });
-}, Util.makeDom(
- /*html*/
- `
-
-`
-), Util.makeDom(
- /*html*/
- ``
-));
-UI$1.toast = function(message, options = {}) {
- const delay = options.delay ?? 5e3;
- const t = document.createElement("Toast");
- t.state = { delay, left: delay ? delay / 1e3 : void 0, type: options.type || "primary", message, buttons: options.buttons || [] };
- $(`[toast-container="${options.container || "default"}"]`).appendChild(t);
- Promise.resolve().then(() => t.show());
-};
-UI$1.toastConfirm = function(message, options = {}) {
- return new Promise((resolve) => UI$1.toast(message, { buttons: ["{#Confirm#}"], ...options }).then((index2) => resolve(index2 === 1)).catch(() => resolve(false)));
-};
-Component.register("AutoForm", (container) => {
- if (!container.state.schema) container.state.schema = [];
- const ensureProxy = (v) => v && typeof v === "object" && !v.__isProxy ? NewState(v) : v;
- container.state.__watch("data", (v) => container.data = ensureProxy(v));
- container.data = ensureProxy(container.state.data || {});
- container.vertical = container.hasAttribute("vertical");
- container.inline = container.hasAttribute("inline");
- container.request = { method: "POST" };
- container.response = {};
- container.result = null;
- if (container.inline) {
- State.__watch("editingData", (data) => {
- container.data = data;
- });
- State.__watch("editingSchema", (schema) => {
- container.state.schema = schema;
- });
- }
- container.form = $(container, "form");
- container.submit = (opt = {}) => {
- var _a, _b;
- if (!container.form.reportValidity()) return (_b = (_a = globalThis.UI) == null ? void 0 : _a.toast) == null ? void 0 : _b.call(_a, "{#verify failed#}", { type: "danger" });
- if (!container.dispatchEvent(new CustomEvent("submit", { detail: container.data, cancelable: true, bubbles: false }))) return;
- const req = { ...container.request, data: container.data, noui: true, ...opt };
- let task = null;
- if (container.api) task = container.api.do(req);
- else if (container.request.url) task = HTTP.request(req);
- else return console.warn("{#please config .api or .request.url to auto submit#}");
- task.then((resp) => {
- container.response = resp;
- container.result = resp.result;
- if (typeof resp.result === "object" && resp.result.error) throw new Error(resp.result.error);
- container.dispatchEvent(new CustomEvent("response", { detail: resp, bubbles: false }));
- }).catch((err) => {
- var _a2;
- if ((_a2 = globalThis.UI) == null ? void 0 : _a2.toast) UI.toast(err.message, { type: "danger" });
- container.dispatchEvent(new CustomEvent("error", { detail: err, bubbles: true }));
- });
- };
-}, Util.makeDom(
- /*html*/
- `
-
-`
-), Util.makeDom(
- /*html*/
- ``
-));
-const AutoForm = {
- customTypes: [],
- register: (name, typeName) => {
- const type = typeName || name;
- if (!AutoForm.customTypes.find((t) => t.name === name)) {
- AutoForm.customTypes.push({ name, typeName: type });
- }
- }
-};
-Component.register("TagsInput", (container) => {
- container.state = NewState({ tags: [] });
- container.addEventListener("bind", (e) => {
- container.state.tags = Array.isArray(e.detail) ? e.detail : [];
- });
- Object.defineProperty(container, "value", {
- get: () => container.state.tags,
- set: (v) => {
- container.state.tags = Array.isArray(v) ? v : [];
- }
- });
-}, Util.makeDom(
- /*html*/
- `
-
-
-
-
-`
-), Util.makeDom(
- /*html*/
- ``
-));
-AutoForm.register("TagsInput");
-Component.register("DatePicker", (container) => {
- container.state = NewState({ start: "", end: "" });
- container.addEventListener("bind", (e) => {
- var _a, _b, _c;
- container.state.start = e.detail || "";
- const form = container.closest("AutoForm");
- const name = container.getAttribute("name");
- const item = (_b = (_a = form == null ? void 0 : form.state) == null ? void 0 : _a.schema) == null ? void 0 : _b.find((i) => i.name === name);
- const rangeEnd = ((_c = item == null ? void 0 : item.setting) == null ? void 0 : _c.rangeEnd) || container.rangeEnd;
- if (form && rangeEnd) {
- container.state.end = form.data[rangeEnd] || "";
- }
- });
- Object.defineProperty(container, "isRange", {
- get: () => {
- var _a, _b, _c;
- const form = container.closest("AutoForm");
- const name = container.getAttribute("name");
- const item = (_b = (_a = form == null ? void 0 : form.state) == null ? void 0 : _a.schema) == null ? void 0 : _b.find((i) => i.name === name);
- return !!(((_c = item == null ? void 0 : item.setting) == null ? void 0 : _c.rangeEnd) || container.rangeEnd);
- }
- });
- Object.defineProperty(container, "value", {
- get: () => container.state.start,
- set: (v) => {
- container.state.start = v || "";
- }
- });
- container.updateStart = (val) => {
- container.state.start = val;
- container.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: val }));
- };
- container.updateEnd = (val) => {
- var _a, _b, _c;
- container.state.end = val;
- const form = container.closest("AutoForm");
- const name = container.getAttribute("name");
- const item = (_b = (_a = form == null ? void 0 : form.state) == null ? void 0 : _a.schema) == null ? void 0 : _b.find((i) => i.name === name);
- const rangeEnd = ((_c = item == null ? void 0 : item.setting) == null ? void 0 : _c.rangeEnd) || container.rangeEnd;
- if (form && rangeEnd) {
- form.data[rangeEnd] = val;
- }
- };
-}, Util.makeDom(
- /*html*/
- `
-
-
-
- -
-
-
-
-`
-));
-Component.register("ColorPicker", (container) => {
- container.state = NewState({ value: "#000000" });
- container.addEventListener("bind", (e) => {
- container.state.value = e.detail || "#000000";
- });
- Object.defineProperty(container, "value", {
- get: () => container.state.value,
- set: (v) => {
- container.state.value = v || "#000000";
- }
- });
- container.updateValue = (val) => {
- container.state.value = val;
- container.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: val }));
- };
-}, Util.makeDom(
- /*html*/
- `
-
-
-
-
-`
-));
-const BOOTSTRAP_ICONS = ["alarm", "archive", "arrow-left", "arrow-right", "bag", "bank", "basket", "bell", "bookmark", "box", "briefcase", "calendar", "camera", "cart", "chat", "check", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "clock", "cloud", "code", "collection", "command", "cpu", "credit-card", "cup", "dash", "database", "display", "door-closed", "download", "droplet", "earbuds", "edit", "egg", "eject", "envelope", "eraser", "eye", "file", "filter", "flag", "folder", "gear", "gem", "gift", "graph-up", "grid", "hammer", "hand-thumbs-up", "heart", "house", "image", "inbox", "info-circle", "journal", "key", "laptop", "layers", "layout-text-sidebar-reverse", "lightbulb", "link", "list", "lock", "map", "mic", "moon", "mouse", "music-note", "newspaper", "palette", "paperclip", "pause", "pencil", "person", "phone", "pie-chart", "play", "plus", "printer", "puzzle", "question-circle", "reception-4", "record", "reply", "rss", "save", "search", "send", "server", "share", "shield", "shop", "shuffle", "skip-end", "skip-start", "slash", "sliders", "smartphone", "speaker", "speedometer", "spellcheck", "square", "star", "stickies", "stop", "stopwatch", "suit-heart", "sun", "table", "tag", "tags", "telephone", "terminal", "text-paragraph", "thermometer", "three-dots", "ticket", "tools", "trash", "trophy", "truck", "tv", "umbrella", "unlock", "upload", "vector-pen", "wallet", "watch", "wifi", "window", "wrench", "x", "zoom-in", "zoom-out", "activity", "at", "award", "backspace", "badge-3d", "badge-4k", "badge-8k", "badge-ad", "badge-ar", "badge-cc", "badge-hd", "badge-tm", "badge-vo", "badge-vr", "badge-wc", "bar-chart", "battery", "bicycle", "binoculars", "blockquote-left", "blockquote-right", "book", "bookshelf", "bootstrap", "border-all", "border-bottom", "border-center", "border-inner", "border-left", "border-middle", "border-outer", "border-right", "border-style", "border-top", "border-width", "bounding-box", "box-arrow-down", "box-arrow-in-down", "box-arrow-in-left", "box-arrow-in-right", "box-arrow-in-up", "box-arrow-left", "box-arrow-right", "box-arrow-up", "box-seam", "brightness-alt-high", "brightness-alt-low", "brightness-high", "brightness-low", "broadcast", "brush", "bucket", "bug", "building", "bullseye", "calculator", "calendar-check", "calendar-date", "calendar-day", "calendar-event", "calendar-minus", "calendar-month", "calendar-plus", "calendar-range", "calendar-week", "calendar-x", "calendar2", "calendar3", "calendar4", "camera-reels", "camera-video", "capslock", "card-checklist", "card-heading", "card-image", "card-list", "card-text", "caret-down", "caret-left", "caret-right", "caret-up", "cart-check", "cart-dash", "cart-plus", "cart-x", "cash", "cash-stack", "cast", "chat-dots", "chat-left", "chat-quote", "chat-right", "chat-square", "chat-text", "check-all", "check-circle", "check-square", "circle", "clipboard", "cloud-arrow-down", "cloud-arrow-up", "cloud-check", "cloud-download", "cloud-fog", "cloud-hail", "cloud-lightning", "cloud-minus", "cloud-moon", "cloud-plus", "cloud-rain", "cloud-slash", "cloud-snow", "cloud-sun", "cloud-upload", "clouds", "cloudy", "code-slash", "code-square", "collection-play", "columns", "columns-gap", "compass", "cone", "cone-striped", "controller", "credit-card-2-back", "credit-card-2-front", "crop", "cup-straw", "cursor", "dash-circle", "dash-square", "diagram-2", "diagram-3", "diamond", "dice-1", "dice-2", "dice-3", "dice-4", "dice-5", "dice-6", "disc", "discord", "distribute-horizontal", "distribute-vertical", "door-open", "dot", "droplet-half", "easel", "egg-fried", "emoji-angry", "emoji-dizzy", "emoji-expressionless", "emoji-frown", "emoji-heart-eyes", "emoji-laughing", "emoji-neutral", "emoji-smile", "emoji-sunglasses", "emoji-wink", "envelope-open", "exclamation", "exclamation-circle", "exclamation-diamond", "exclamation-octagon", "exclamation-square", "exclamation-triangle", "eye-slash", "eyedropper", "facebook", "file-arrow-down", "file-arrow-up", "file-binary", "file-break", "file-check", "file-code", "file-diff", "file-earmark", "file-excel", "file-image", "file-lock", "file-medical", "file-minus", "file-music", "file-pdf", "file-person", "file-play", "file-plus", "file-post", "file-ppt", "file-richtext", "file-slides", "file-spreadsheet", "file-text", "file-word", "file-zip", "files", "film", "filter-circle", "filter-left", "filter-right", "filter-square", "fingerprint", "flower1", "flower2", "flower3", "folder-check", "folder-minus", "folder-plus", "folder-symlink", "folder-x", "folder2-open", "fonts", "forward", "front", "fullscreen", "fullscreen-exit", "funnel", "gear-wide", "gender-female", "gender-male", "gender-trans", "geo", "geo-alt", "github", "globe", "google", "graph-down", "grid-1x2", "grid-3x2", "grid-3x3", "grip-horizontal", "grip-vertical", "hand-index", "hand-thumbs-down", "handbag", "hash", "headphones", "headset", "heart-half", "heptagon", "hourglass", "hourglass-bottom", "hourglass-split", "hourglass-top", "house-door", "hr", "hurricane", "image-alt", "images", "infinity", "input-cursor", "instagram", "intersect", "journal-album", "journal-arrow-down", "journal-arrow-up", "journal-bookmark", "journal-check", "journal-code", "journal-medical", "journal-minus", "journal-plus", "journal-richtext", "journal-text", "journal-x", "journals", "justify", "kanban", "keyboard", "ladder", "lamp", "layers-half", "layout-sidebar", "layout-split", "layout-three-columns", "life-preserver", "lightbulb-off", "lightning", "lightning-charge", "link-45deg", "linkedin", "list-check", "list-nested", "list-ol", "list-stars", "list-task", "list-ul", "mailbox", "markdown", "mask", "mastodon", "megaphone", "menu-app", "menu-button", "messenger", "mic-mute", "minecart", "minecart-loaded", "moisture", "mouse2", "mouse3", "music-note-beamed", "music-note-list", "music-player", "node-minus", "node-plus", "nut", "octagon", "option", "outlet", "paint-bucket", "patch-check", "patch-exclamation", "patch-minus", "patch-plus", "patch-question", "pause-btn", "pause-circle", "peace", "pen", "pencil-square", "pentagon", "person-badge", "person-bounding-box", "person-circle", "person-lines-fill", "phone-landscape", "phone-vibrate", "pie-chart-fill", "pin", "pin-angle", "pin-fill", "pin-map", "pip", "play-btn", "play-circle", "plug", "plus-circle", "plus-square", "power", "question", "question-diamond", "question-square", "rainbow", "receipt", "receipt-cutoff", "reception-0", "reception-1", "reception-2", "reception-3", "record-btn", "record-circle", "record2", "recycle", "reddit", "reply-all", "router", "rulers", "safe", "save2", "sd-card", "segmented-nav", "shield-check", "shield-exclamation", "shield-lock", "shield-shaded", "shield-slash", "shift", "signpost", "signpost-2", "signpost-split", "sim", "skip-backward", "skip-forward", "slack", "slash-circle", "slash-square", "smartwatch", "snow", "snow2", "snow3", "sort-alpha-down", "sort-alpha-up", "sort-numeric-down", "sort-numeric-up", "soundwave", "speedometer2", "square-half", "stack", "star-half", "stars", "stop-btn", "stop-circle", "suit-club", "suit-diamond", "suit-spade", "sunglasses", "sunrise", "sunset", "symmetry-horizontal", "symmetry-vertical", "tablet", "tablet-landscape", "telegram", "telephone-forward", "telephone-inbound", "telephone-outbound", "telephone-plus", "telephone-x", "text-center", "text-indent-left", "text-indent-right", "text-left", "text-right", "thermometer-half", "thermometer-high", "thermometer-low", "thermometer-snow", "thermometer-sun", "three-dots-vertical", "toggle-off", "toggle-on", "toggle2-off", "toggle2-on", "tornado", "translate", "trash2", "tree", "truck-flatbed", "tsunami", "type", "type-bold", "type-h1", "type-h2", "type-h3", "type-italic", "type-strikethrough", "type-underline", "ui-checks", "ui-checks-grid", "ui-radios", "ui-radios-grid", "union", "upc", "upc-scan", "view-list", "view-stacked", "vinyl", "voicemail", "volume-down", "volume-mute", "volume-off", "volume-up", "vr", "wallet2", "water", "whatsapp", "wifi-1", "wifi-2", "wifi-off", "wind", "window-dock", "window-sidebar", "x-circle", "x-diamond", "x-octagon", "x-square", "youtube"];
-Component.register("IconPicker", (container) => {
- container.state = NewState({ value: "", search: "", open: false });
- container.addEventListener("bind", (e) => {
- container.state.value = e.detail || "";
- });
- Object.defineProperty(container, "value", {
- get: () => container.state.value,
- set: (v) => {
- container.state.value = v || "";
- }
- });
- Object.defineProperty(container, "filteredIcons", {
- get: () => {
- var _a;
- const s = ((_a = container.state.search) == null ? void 0 : _a.toLowerCase()) || "";
- return BOOTSTRAP_ICONS.filter((i) => i.includes(s));
- }
- });
- container.selectIcon = (icon) => {
- container.state.value = icon;
- container.state.open = false;
- container.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: icon }));
- };
- container.toggle = () => {
- container.state.open = !container.state.open;
- if (container.state.open) {
- setTimeout(() => {
- var _a;
- (_a = $(container, "input")) == null ? void 0 : _a.focus();
- }, 10);
- }
- };
- const onGlobalClick = (e) => {
- if (!container.contains(e.target)) {
- container.state.open = false;
- }
- };
- window.addEventListener("click", onGlobalClick);
- container.addEventListener("remove", () => window.removeEventListener("click", onGlobalClick));
-}, Util.makeDom(
- /*html*/
- `
-
-
-
-
-
-
-
-
-
No icons found
-
-
-
-`
-), Util.makeDom(
- /*html*/
- ``
-));
-AutoForm.register("DatePicker");
-AutoForm.register("ColorPicker");
-AutoForm.register("IconPicker");
-const VirtualScroll = (options = {}) => {
- const itemHeights = /* @__PURE__ */ new Map();
- const groupHeights = /* @__PURE__ */ new Map();
- let groupItemCount = 1;
- const avg = Util.newAvg();
- let padTop = 0, rowGap = 0, topMargin = 0, itemMarginTop = null, itemMarginBottom = null, listInited = false;
- const providedItemHeight = options.itemHeight || null;
- return {
- reset: (list, container) => {
- listInited = false;
- itemHeights.clear();
- groupHeights.clear();
- avg.clear();
- topMargin = 0;
- itemMarginTop = null;
- itemMarginBottom = null;
- if (!(list == null ? void 0 : list.length)) return [];
- const size = list.length;
- groupItemCount = Math.ceil(Math.sqrt(size)) || 10;
- const style = window.getComputedStyle(container);
- padTop = parseFloat(style.paddingTop) || 0;
- rowGap = parseFloat(style.rowGap) || 0;
- const visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / (providedItemHeight || 32)));
- return list.slice(0, Math.min(visibleCount * 3, size));
- },
- init: (list, refreshCallback) => {
- if (listInited) return;
- const size = list.length;
- let defaultHeight = providedItemHeight || avg.get() || 32;
- if (size > 0 && typeof list[0] === "object" && list[0] !== null && list[0]._itemHeight) {
- defaultHeight = list[0]._itemHeight;
- }
- avg.add(defaultHeight);
- if (itemMarginTop === null) {
- itemMarginTop = 0;
- itemMarginBottom = 0;
- }
- for (let i = 0; i < size; i++) {
- if (!itemHeights.has(i)) {
- const ih = typeof list[i] === "object" && list[i] !== null && list[i]._itemHeight ? list[i]._itemHeight : defaultHeight;
- itemHeights.set(i, ih);
- }
- }
- for (let i = 0; i < size; i += groupItemCount) groupHeights.set(i, Math.min(groupItemCount, size - i) * defaultHeight);
- listInited = true;
- refreshCallback();
- },
- update: (absoluteIndex, node) => {
- if (node.offsetHeight === 0) return;
- if (itemMarginTop === null) {
- const style = window.getComputedStyle(node);
- itemMarginTop = parseFloat(style.marginTop) || 0;
- itemMarginBottom = parseFloat(style.marginBottom) || 0;
- }
- if (absoluteIndex === 0 && !topMargin) topMargin = itemMarginTop;
- const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + rowGap;
- const oldHeight = itemHeights.get(absoluteIndex);
- if (newHeight !== oldHeight) {
- itemHeights.set(absoluteIndex, newHeight);
- avg.add(newHeight);
- const offset = newHeight - (oldHeight || 0), groupIndex = absoluteIndex - absoluteIndex % groupItemCount;
- if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
- }
- },
- calc: (container, list) => {
- if (!listInited || !list) return null;
- const size = list.length;
- const avgVal = Math.max(16, avg.get() || 32);
- let visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / avgVal));
- let prev = padTop + topMargin + rowGap, post = 0, status = 0, listStartIndex = 0, listEndIndex = 0;
- let renderedList = [];
- const scrollTop = container.scrollTop;
- let loopCount = 0;
- for (let i = 0; i < size; i++) {
- if (++loopCount > size * 2) {
- throw new Error(`VirtualScroll.calc infinite loop detected at i=${i}, status=${status}, size=${size}, groupItemCount=${groupItemCount}`);
- }
- if (status === 0) {
- const gh = groupHeights.get(i);
- if (gh && prev + gh <= scrollTop && i + groupItemCount < size) {
- prev += gh;
- i += groupItemCount - 1;
- } else {
- const ih = itemHeights.get(i);
- if (prev + ih <= scrollTop && i < size - 1) {
- prev += ih;
- } else {
- status = 1;
- let visibleStartIndex = Math.max(0, i);
- listStartIndex = Math.max(0, visibleStartIndex - visibleCount);
- listEndIndex = Math.min(listStartIndex + visibleCount * 3, size);
- i = listEndIndex - 1;
- renderedList = list.slice(listStartIndex, listEndIndex);
- for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j);
- }
- }
- } else if (status === 1) {
- const gh = groupHeights.get(i);
- if (gh) {
- post += gh;
- i += groupItemCount - 1;
- } else {
- post += itemHeights.get(i);
- }
- }
- }
- const finalPrevHeight = Math.max(0, prev - padTop - topMargin - rowGap - (listStartIndex > 0 ? rowGap : 0));
- const finalPostHeight = post > 0 ? Math.max(0, post - 2 * rowGap) : 0;
- return { prevHeight: finalPrevHeight, postHeight: finalPostHeight, renderedList, listStartIndex };
- }
- };
-};
-Component.register("List", (container) => {
- container.mode = container.getAttribute("mode") || "normal";
- container.fast = container.hasAttribute("fast");
- container.collapsible = container.hasAttribute("collapsible");
- const padTopEl = container.fast ? container.querySelector(".vs-pad-top") : null;
- const padBottomEl = container.fast ? container.querySelector(".vs-pad-bottom") : null;
- const defaultSets = {
- idfield: "id",
- labelfield: "label",
- summaryfield: "summary",
- groupidfield: "id",
- grouplabelfield: "label",
- groupsummaryfield: "summary",
- groupfield: "group",
- parentfield: "parent",
- groupicon: "folder",
- itemicon: "file"
- };
- container.collapsed = NewState({});
- const updateFlatList = () => {
- Util.updateDefaults(container, defaultSets);
- const list = container.state.list || [], flatList = [];
- if (container.mode === "group") {
- const itemMap = {};
- list.forEach((item) => {
- var _a;
- return (itemMap[_a = item[container.groupfield]] ?? (itemMap[_a] = [])).push(item);
- });
- (container.state.groups || []).forEach((group) => {
- flatList.push({ type: "group", ...group });
- const items = itemMap[group[container.groupidfield]];
- if (items) items.forEach((item) => flatList.push({ type: "item", ...item }));
- });
- } else if (container.mode === "tree") {
- const childrenMap = {};
- list.forEach((item) => {
- var _a;
- return (childrenMap[_a = item[container.parentfield] || ""] ?? (childrenMap[_a] = [])).push(item);
- });
- const traverse = (items, level, parents) => items.forEach((item) => {
- var _a;
- const id = item[container.idfield], hasChildren = !!((_a = childrenMap[id]) == null ? void 0 : _a.length);
- const isCollapsed = container.collapsed[id];
- flatList.push({ type: "item", ...item, _level: level, _hasChildren: hasChildren, _parents: parents });
- if (hasChildren && !isCollapsed) traverse(childrenMap[id], level + 1, [...parents, id]);
- });
- traverse(childrenMap[""] || [], 0, []);
- } else list.forEach((item) => flatList.push({ type: "item", ...item }));
- container.state._flatList = flatList;
- };
- container.state.__watch("list", updateFlatList);
- const vs = container.fast ? VirtualScroll() : null;
- container.state._renderedList = [];
- let refreshing = false;
- container.refresh = () => {
- if (!container.fast || refreshing) return;
- refreshing = true;
- try {
- const res = vs.calc(container, container.state._flatList);
- if (res) {
- if (padTopEl) padTopEl.style.height = `${res.prevHeight}px`;
- if (padBottomEl) padBottomEl.style.height = `${res.postHeight}px`;
- if (container.state._listStartIndex !== res.listStartIndex) {
- container.state._listStartIndex = res.listStartIndex;
- }
- const cur = container.state._renderedList || [];
- if (cur.length !== res.renderedList.length || cur[0] !== res.renderedList[0] || cur[cur.length - 1] !== res.renderedList[res.renderedList.length - 1]) {
- container.state._renderedList = res.renderedList;
- }
- }
- } finally {
- setTimeout(() => {
- refreshing = false;
- }, 0);
- }
- };
- container.onItemUpdate = (index2, node) => {
- if (container.fast) vs.update(index2 + (container.state._listStartIndex || 0), node);
- };
- container.state.__watch("_flatList", (flatList) => {
- if (container.fast) {
- if (padTopEl) padTopEl.style.height = "0px";
- if (padBottomEl) padBottomEl.style.height = "0px";
- container.state._listStartIndex = 0;
- container.state._renderedList = vs.reset(flatList, container) || [];
- setTimeout(() => {
- if (container.state._flatList === flatList) vs.init(flatList, container.refresh);
- });
- } else container.state._renderedList = flatList;
- });
- container.selectItem = (item, index2) => {
- if (container.hasAttribute("auto-select")) container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield];
- container.dispatchEvent(new CustomEvent("itemclick", { bubbles: false, detail: { item, index: index2 + (container.fast ? container.state._listStartIndex || 0 : 0) } }));
- };
- container.selectGroup = (item, index2) => {
- if (container.hasAttribute("auto-select-group")) container.state.selectedGroup = container.state.selectedGroup === item[container.groupidfield] ? null : item[container.groupidfield];
- container.dispatchEvent(new CustomEvent("groupclick", { bubbles: false, detail: { item, index: index2 } }));
- };
- container.toggleCollapse = (item) => {
- if (container.collapsible && item._hasChildren) {
- container.collapsed[item[container.idfield]] = !container.collapsed[item[container.idfield]];
- updateFlatList();
- }
- };
- updateFlatList();
-}, Util.makeDom(
- /*html*/
- `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`
-));
-Component.register("Nav", (container) => {
- container.vertical = container.hasAttribute("vertical");
- container.click = (item, noselect) => {
- if (!item.noselect && !noselect) Hash.nav = item.name;
- container.dispatchEvent(new CustomEvent("nav", { detail: { item }, bubbles: false }));
- };
-}, Util.makeDom(
- /*html*/
- `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`
-));
-let _mouseMoverMoving = false;
-let _mouseMoverPos = {};
-let _mouseMoverEvents = {};
-const MouseMover = {
- start: (event, { onmousemove, onmouseup }) => {
- _mouseMoverPos = { x: event.clientX, y: event.clientY, w: 0, h: 0 };
- _mouseMoverEvents = { onmousemove, onmouseup };
- _mouseMoverMoving = true;
- }
-};
-if (typeof document !== "undefined") {
- document.addEventListener("mouseup", (event) => {
- var _a;
- if (!_mouseMoverMoving) return;
- _mouseMoverMoving = false;
- (_a = _mouseMoverEvents.onmouseup) == null ? void 0 : _a.call(_mouseMoverEvents, { event, ..._mouseMoverPos });
- });
- document.addEventListener("mousemove", (event) => {
- var _a;
- if (!_mouseMoverMoving) return;
- _mouseMoverPos.w = event.clientX - _mouseMoverPos.x;
- _mouseMoverPos.h = event.clientY - _mouseMoverPos.y;
- (_a = _mouseMoverEvents.onmousemove) == null ? void 0 : _a.call(_mouseMoverEvents, { event, ..._mouseMoverPos });
- });
-}
-Component.register("Resizer", (container) => {
- container.isVertical = container.hasAttribute("vertical");
- const min = parseInt(container.getAttribute("min")) || 10;
- const max = parseInt(container.getAttribute("max")) || 1e3;
- const target = container.target || container.previousElementSibling;
- container.addEventListener("bind", (e) => {
- if (e.detail !== void 0 && e.detail !== null) {
- target.style[container.isVertical ? "height" : "width"] = e.detail + "px";
- }
- });
- const getSize = (startSize, w, h) => {
- const newSize = startSize + (container.isVertical ? h : w);
- return newSize < min ? min : newSize > max ? max : newSize;
- };
- container.addEventListener("mousedown", (event) => {
- const startSize = container.isVertical ? target.offsetHeight : target.offsetWidth;
- MouseMover.start(event, {
- onmousemove: ({ w, h }) => {
- const newSize = getSize(startSize, w, h);
- target.style[container.isVertical ? "height" : "width"] = newSize + "px";
- container.dispatchEvent(new CustomEvent("resizing", { detail: { oldSize: startSize, newSize }, bubbles: false }));
- },
- onmouseup: ({ w, h }) => {
- const newSize = getSize(startSize, w, h);
- container.dispatchEvent(new CustomEvent("resize", { detail: { oldSize: startSize, newSize }, bubbles: false }));
- container.dispatchEvent(new CustomEvent("change", { detail: newSize, bubbles: false }));
- }
- });
- });
-}, Util.makeDom(
- /*html*/
- `
-
-`
-));
-if (typeof window !== "undefined") {
- window.addEventListener("beforeunload", (event) => {
- if (State.exitBlocks > 0) event.preventDefault();
- });
-}
-const htmlNode = document.documentElement;
-if (!htmlNode.hasAttribute("$data-bs-theme") && !htmlNode.hasAttribute("data-bs-theme")) {
- htmlNode.setAttribute("$data-bs-theme", "LocalStorage.darkMode?'dark':'light'");
-}
-globalThis.HTTP = HTTP;
-globalThis.UI = UI$1;
-globalThis.AutoForm = AutoForm;
-globalThis.MouseMover = MouseMover;
-globalThis.VirtualScroll = VirtualScroll;
-const ApigoBase = {
- HTTP,
- UI: UI$1,
- AutoForm,
- MouseMover,
- VirtualScroll,
- State,
- List: VirtualScroll
-};
-if (typeof document !== "undefined") {
- globalThis.ApigoBase = ApigoBase;
-}
-export {
- APIComponent,
- AutoForm,
- HTTP,
- MouseMover,
- State2 as State,
- UI$1 as UI,
- VirtualScroll
-};
diff --git a/package.json b/package.json
index ed20e80..bbb8c79 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,8 @@
{
"name": "@apigo.cc/base",
- "version": "1.0.11",
+ "version": "1.0.13",
"type": "module",
"main": "dist/base.js",
- "module": "dist/base.js",
"files": [
"dist"
],
diff --git a/src/controls.js b/src/controls.js
index d8fc528..4dba31f 100644
--- a/src/controls.js
+++ b/src/controls.js
@@ -6,6 +6,7 @@ import { AutoForm } from './form.js'
* 支持单日期及范围选择 (主字段 + 影子字段模式)
*/
Component.register('DatePicker', container => {
+ container._thisObj = container;
container.state = NewState({ start: '', end: '' })
container.addEventListener('bind', e => {
@@ -63,6 +64,7 @@ Component.register('DatePicker', container => {
* 支持颜色选择与十六进制文本输入
*/
Component.register('ColorPicker', container => {
+ container._thisObj = container;
container.state = NewState({ value: '#000000' })
container.addEventListener('bind', e => {
container.state.value = e.detail || '#000000'
@@ -77,8 +79,8 @@ Component.register('ColorPicker', container => {
}
}, Util.makeDom(/*html*/`
-
-
+
+
`))
@@ -89,6 +91,7 @@ const BOOTSTRAP_ICONS = ['alarm', 'archive', 'arrow-left', 'arrow-right', 'bag',
* 基于 Bootstrap Icons 的可视化选择控件
*/
Component.register('IconPicker', container => {
+ container._thisObj = container;
container.state = NewState({ value: '', search: '', open: false })
container.addEventListener('bind', e => {
container.state.value = e.detail || ''
diff --git a/src/form.js b/src/form.js
index 488460a..53278af 100644
--- a/src/form.js
+++ b/src/form.js
@@ -1,30 +1,81 @@
import { Component, NewState, Util, $, State } from '@apigo.cc/state'
import { HTTP } from './http.js'
+/**
+ * AutoForm 蓝图定义
+ */
+const AUTOFORM_BLUEPRINT = Util.makeDom(/*html*/`
+
+`)
+
+const AUTOFORM_STYLE = Util.makeDom(/*html*/``)
+
Component.register('AutoForm', container => {
if (!container.state.schema) container.state.schema = []
- // TRY: 确保 state.data 始终是响应式 Proxy,支持深层嵌套属性的监听
const ensureProxy = v => (v && typeof v === 'object' && !v.__isProxy) ? NewState(v) : v;
container.state.__watch('data', v => container.data = ensureProxy(v));
container.data = ensureProxy(container.state.data || {});
container.vertical = container.hasAttribute('vertical')
+ container.horizontal = container.hasAttribute('horizontal')
container.inline = container.hasAttribute('inline')
+ container.nobutton = container.hasAttribute('nobutton')
container.request = { method: 'POST' }
container.response = {}
container.result = null
- // 初始化数据联动:如果处于行编辑模式(inline),监听全局编辑状态
- if (container.inline) {
- State.__watch('editingData', (data) => {
- container.data = data
- })
- State.__watch('editingSchema', (schema) => {
- container.state.schema = schema
- })
- }
-
container.form = $(container, 'form')
container.submit = (opt = {}) => {
if (!container.form.reportValidity()) return globalThis.UI?.toast?.('{#verify failed#}', { type: 'danger' })
@@ -44,76 +95,26 @@ Component.register('AutoForm', container => {
container.dispatchEvent(new CustomEvent('error', { detail: err, bubbles: true }))
})
}
-}, Util.makeDom(/*html*/`
-
-
-
-`), Util.makeDom(/*html*/``))
+
+ container.checkIf = (item) => {
+ if (!item.if) return true
+ try {
+ const fn = new Function('Hash', 'LocalStorage', 'State', 'item', 'data', 'return ' + item.if)
+ return fn.call(container, globalThis.Hash, globalThis.LocalStorage, globalThis.State, item, container.data)
+ } catch (e) {
+ return false
+ }
+ }
+}, AUTOFORM_BLUEPRINT, AUTOFORM_STYLE)
+
+const findAnchorInBlueprint = (root) => {
+ let f = root.querySelector('[control-wrapper]');
+ if (f) return f;
+ for (const t of root.querySelectorAll('template')) {
+ f = findAnchorInBlueprint(t.content); if (f) return f;
+ }
+ return null;
+}
export const AutoForm = {
customTypes: [],
@@ -121,54 +122,57 @@ export const AutoForm = {
const type = typeName || name
if (!AutoForm.customTypes.find(t => t.name === name)) {
AutoForm.customTypes.push({ name, typeName: type })
+ AutoForm._addAutoFormComponent(name, type)
}
- }
+ },
+ _addAutoFormComponent: (name, type) => {
+ const wrapper = findAnchorInBlueprint(AUTOFORM_BLUEPRINT)
+ if (wrapper) {
+ const node = Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${type.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="this.data[item.name]" class="w-100">${name}>`)
+ wrapper.appendChild(node)
+ }
+ }
}
+/**
+ * TagsInput
+ * 100% 还原 base_original.js 逻辑与写法
+ */
Component.register('TagsInput', container => {
- container.state = NewState({ tags: [] })
+ container._thisObj = container; // 夺回上下文主权
container.addEventListener('bind', e => {
container.state.tags = Array.isArray(e.detail) ? e.detail : []
})
- // Handle direct property access
- Object.defineProperty(container, 'value', {
- get: () => container.state.tags,
- set: v => { container.state.tags = Array.isArray(v) ? v : []; }
- })
}, Util.makeDom(/*html*/`
-
-
+
+
+ })}'>
-`), Util.makeDom(/*html*/``))
+`), Util.makeDom(/*html*/``))
AutoForm.register('TagsInput')
diff --git a/src/list.js b/src/list.js
index 5c207a6..a8d1a29 100644
--- a/src/list.js
+++ b/src/list.js
@@ -16,9 +16,6 @@ export const VirtualScroll = (options = {}) => {
const size = list.length; groupItemCount = Math.ceil(Math.sqrt(size)) || 10;
const style = window.getComputedStyle(container);
padTop = parseFloat(style.paddingTop) || 0; rowGap = parseFloat(style.rowGap) || 0;
- // Optimization: Give a reasonably large initial buffer instead of just 30 to prevent the
- // "first scroll unresponsiveness" where the user scrolls past the small initial slice
- // before the init() callback has time to map all heights.
const visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / (providedItemHeight || 32)));
return list.slice(0, Math.min(visibleCount * 3, size));
},
@@ -26,19 +23,13 @@ export const VirtualScroll = (options = {}) => {
if (listInited) return;
const size = list.length;
let defaultHeight = providedItemHeight || avg.get() || 32;
-
- // Optimization: If the first item declares a fixed height, use it as the global baseline.
- // This completely bypasses the need for DOM measurement (node.offsetHeight) in fixed-height scenarios.
if (size > 0 && typeof list[0] === 'object' && list[0] !== null && list[0]._itemHeight) {
defaultHeight = list[0]._itemHeight;
}
-
avg.add(defaultHeight);
if (itemMarginTop === null) { itemMarginTop = 0; itemMarginBottom = 0; }
-
for (let i = 0; i < size; i++) {
if (!itemHeights.has(i)) {
- // Fallback to individual item height if specified, otherwise global default
const ih = (typeof list[i] === 'object' && list[i] !== null && list[i]._itemHeight) ? list[i]._itemHeight : defaultHeight;
itemHeights.set(i, ih);
}
@@ -72,14 +63,11 @@ export const VirtualScroll = (options = {}) => {
let loopCount = 0;
for (let i = 0; i < size; i++) {
- if (++loopCount > size * 2) {
- throw new Error(`VirtualScroll.calc infinite loop detected at i=${i}, status=${status}, size=${size}, groupItemCount=${groupItemCount}`);
- }
+ if (++loopCount > size * 2) throw new Error('VirtualScroll infinite loop');
if (status === 0) {
const gh = groupHeights.get(i);
if (gh && prev + gh <= scrollTop && (i + groupItemCount < size)) {
- prev += gh;
- i += groupItemCount - 1;
+ prev += gh; i += groupItemCount - 1;
} else {
const ih = itemHeights.get(i);
if (prev + ih <= scrollTop && i < size - 1) {
@@ -96,19 +84,11 @@ export const VirtualScroll = (options = {}) => {
}
} else if (status === 1) {
const gh = groupHeights.get(i);
- if (gh) {
- post += gh;
- i += groupItemCount - 1;
- } else {
- post += itemHeights.get(i);
- }
+ if (gh) { post += gh; i += groupItemCount - 1; }
+ else post += itemHeights.get(i);
}
}
- // Fix for flex gap inflation:
- // prevHeight block participates in flex gap, so we subtract one rowGap.
const finalPrevHeight = Math.max(0, prev - padTop - topMargin - rowGap - (listStartIndex > 0 ? rowGap : 0));
- // post accumulated M * (h + gap). Real space is sum(h) + (M-1)*gap = post - gap.
- // DOM adds one gap before the block, so we need postHeight + gap = post - gap => postHeight = post - 2*gap.
const finalPostHeight = post > 0 ? Math.max(0, post - 2 * rowGap) : 0;
return { prevHeight: finalPrevHeight, postHeight: finalPostHeight, renderedList, listStartIndex };
}
@@ -129,7 +109,8 @@ Component.register('List', container => {
parentfield: 'parent', groupicon: 'folder', itemicon: 'file'
}
container.collapsed = NewState({})
-
+ container.state.renderedList = []
+
const updateFlatList = () => {
Util.updateDefaults(container, defaultSets)
const list = container.state.list || [], flatList = []
@@ -152,52 +133,44 @@ Component.register('List', container => {
})
traverse(childrenMap[''] || [], 0, [])
} else list.forEach(item => flatList.push({ type: 'item', ...item }))
- container.state._flatList = flatList
+ container.state.flatList = flatList
}
container.state.__watch('list', updateFlatList)
const vs = container.fast ? VirtualScroll() : null
- container.state._renderedList = []
let refreshing = false
container.refresh = () => {
if (!container.fast || refreshing) return
refreshing = true
try {
- const res = vs.calc(container, container.state._flatList)
+ const res = vs.calc(container, container.state.flatList)
if (res) {
if (padTopEl) padTopEl.style.height = `${res.prevHeight}px`
if (padBottomEl) padBottomEl.style.height = `${res.postHeight}px`
-
- if (container.state._listStartIndex !== res.listStartIndex) {
- container.state._listStartIndex = res.listStartIndex
- }
-
- const cur = container.state._renderedList || []
- if (cur.length !== res.renderedList.length || cur[0] !== res.renderedList[0] || cur[cur.length - 1] !== res.renderedList[res.renderedList.length - 1]) {
- container.state._renderedList = res.renderedList
- }
+ container.state.listStartIndex = res.listStartIndex
+ container.state.renderedList = res.renderedList
}
} finally {
setTimeout(() => { refreshing = false }, 0)
}
}
- container.onItemUpdate = (index, node) => { if (container.fast) vs.update(index + (container.state._listStartIndex || 0), node) }
+ container.onItemUpdate = (index, node) => { if (container.fast) vs.update(index + (container.state.listStartIndex || 0), node) }
- container.state.__watch('_flatList', flatList => {
+ container.state.__watch('flatList', flatList => {
if (container.fast) {
if (padTopEl) padTopEl.style.height = '0px'
if (padBottomEl) padBottomEl.style.height = '0px'
- container.state._listStartIndex = 0
- container.state._renderedList = vs.reset(flatList, container) || []
- setTimeout(() => { if (container.state._flatList === flatList) vs.init(flatList, container.refresh) })
- } else container.state._renderedList = flatList
+ container.state.listStartIndex = 0
+ container.state.renderedList = vs.reset(flatList, container) || []
+ setTimeout(() => { if (container.state.flatList === flatList) vs.init(flatList, container.refresh) })
+ } else container.state.renderedList = flatList
})
container.selectItem = (item, index) => {
if (container.hasAttribute('auto-select')) container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield]
- container.dispatchEvent(new CustomEvent('itemclick', { bubbles: false, detail: { item, index: index + (container.fast ? (container.state._listStartIndex || 0) : 0) } }))
+ container.dispatchEvent(new CustomEvent('itemclick', { bubbles: false, detail: { item, index: index + (container.fast ? (container.state.listStartIndex || 0) : 0) } }))
}
container.selectGroup = (item, index) => {
if (container.hasAttribute('auto-select-group')) container.state.selectedGroup = container.state.selectedGroup === item[container.groupidfield] ? null : item[container.groupidfield]
@@ -209,14 +182,14 @@ Component.register('List', container => {
}, Util.makeDom(/*html*/`
-
-
+
+
-
+
@@ -234,7 +207,7 @@ Component.register('List', container => {
-
+
diff --git a/test/capability.html b/test/capability.html
index f1006c2..8f66f05 100644
--- a/test/capability.html
+++ b/test/capability.html
@@ -116,7 +116,7 @@
@@ -144,7 +144,7 @@
diff --git a/test/form_test.html b/test/form_test.html
new file mode 100644
index 0000000..d41a327
--- /dev/null
+++ b/test/form_test.html
@@ -0,0 +1,108 @@
+
+
+
+
+ AutoForm Mega Unit Test
+
+
+
+
+
+
+
+
+
AutoForm Mega Test & Function Showcase
+
+
+
+
+
+
+
+
+
+
+
+
+ Custom Button
+
+
+
+
+
+
+
+
+
+
+
3. Inline Mode Scenarios
+
+
+
Toolbar (No label, with action)
+
+
+
+
Compact Config (With labels, no button)
+
+
+
+
+
+
+
+
+
diff --git a/test/lib/base.js b/test/lib/base.js
index 4e3e4c4..54a45d9 100644
--- a/test/lib/base.js
+++ b/test/lib/base.js
@@ -220,24 +220,79 @@
UI$1.toastConfirm = function(message, options = {}) {
return new Promise((resolve) => UI$1.toast(message, { buttons: ["{#Confirm#}"], ...options }).then((index2) => resolve(index2 === 1)).catch(() => resolve(false)));
};
+ const AUTOFORM_BLUEPRINT = state.Util.makeDom(
+ /*html*/
+ `
+
+`
+ );
+ const AUTOFORM_STYLE = state.Util.makeDom(
+ /*html*/
+ ``
+ );
state.Component.register("AutoForm", (container) => {
if (!container.state.schema) container.state.schema = [];
const ensureProxy = (v) => v && typeof v === "object" && !v.__isProxy ? state.NewState(v) : v;
container.state.__watch("data", (v) => container.data = ensureProxy(v));
container.data = ensureProxy(container.state.data || {});
container.vertical = container.hasAttribute("vertical");
+ container.horizontal = container.hasAttribute("horizontal");
container.inline = container.hasAttribute("inline");
+ container.nobutton = container.hasAttribute("nobutton");
container.request = { method: "POST" };
container.response = {};
container.result = null;
- if (container.inline) {
- state.State.__watch("editingData", (data) => {
- container.data = data;
- });
- state.State.__watch("editingSchema", (schema) => {
- container.state.schema = schema;
- });
- }
container.form = state.$(container, "form");
container.submit = (opt = {}) => {
var _a, _b;
@@ -259,150 +314,87 @@
container.dispatchEvent(new CustomEvent("error", { detail: err, bubbles: true }));
});
};
- }, state.Util.makeDom(
- /*html*/
- `
-
-
-
-`
- ), state.Util.makeDom(
- /*html*/
- ``
- ));
+ container.checkIf = (item) => {
+ if (!item.if) return true;
+ try {
+ const fn = new Function("Hash", "LocalStorage", "State", "item", "data", "return " + item.if);
+ return fn.call(container, globalThis.Hash, globalThis.LocalStorage, globalThis.State, item, container.data);
+ } catch (e) {
+ return false;
+ }
+ };
+ }, AUTOFORM_BLUEPRINT, AUTOFORM_STYLE);
+ const findAnchorInBlueprint = (root) => {
+ let f = root.querySelector("[control-wrapper]");
+ if (f) return f;
+ for (const t of root.querySelectorAll("template")) {
+ f = findAnchorInBlueprint(t.content);
+ if (f) return f;
+ }
+ return null;
+ };
const AutoForm = {
customTypes: [],
register: (name, typeName) => {
const type = typeName || name;
if (!AutoForm.customTypes.find((t) => t.name === name)) {
AutoForm.customTypes.push({ name, typeName: type });
+ AutoForm._addAutoFormComponent(name, type);
+ }
+ },
+ _addAutoFormComponent: (name, type) => {
+ const wrapper = findAnchorInBlueprint(AUTOFORM_BLUEPRINT);
+ if (wrapper) {
+ const node = state.Util.makeDom(`<${name} $if="item.type?.toLowerCase() === '${type.toLowerCase()}'" $name="item.name" $.="item.setting || {}" $bind="this.data[item.name]" class="w-100">${name}>`);
+ wrapper.appendChild(node);
}
}
};
state.Component.register("TagsInput", (container) => {
- container.state = state.NewState({ tags: [] });
+ container._thisObj = container;
container.addEventListener("bind", (e) => {
container.state.tags = Array.isArray(e.detail) ? e.detail : [];
});
- Object.defineProperty(container, "value", {
- get: () => container.state.tags,
- set: (v) => {
- container.state.tags = Array.isArray(v) ? v : [];
- }
- });
}, state.Util.makeDom(
/*html*/
`
-
-
+
+ {
- const buttons = this.querySelectorAll("button");
- if (buttons.length > 0) (buttons[index > 0 ? index - 1 : 0] || buttons[0]).focus();
- else {
- const input = state.$(this, "input");
- if (input) input.focus();
- }
- });
- }
+ this.state.tags.splice(index, 1);
+ this.state.tags = this.state.tags;
+ this.dispatchEvent(new CustomEvent("change", { bubbles: false, detail: this.state.tags }));
+ Promise.resolve().then(() => {
+ const buttons = $$(this, "button");
+ (buttons.length > 0 ? buttons[index > 0 ? index - 1 : 0] : state.$(this, "input")).focus();
+ });
}
- })}" $text="item">
-
+
+
+ })}'>
`
), state.Util.makeDom(
/*html*/
- ``
+ ``
));
AutoForm.register("TagsInput");
state.Component.register("DatePicker", (container) => {
+ container._thisObj = container;
container.state = state.NewState({ start: "", end: "" });
container.addEventListener("bind", (e) => {
var _a, _b, _c;
@@ -458,6 +450,7 @@
`
));
state.Component.register("ColorPicker", (container) => {
+ container._thisObj = container;
container.state = state.NewState({ value: "#000000" });
container.addEventListener("bind", (e) => {
container.state.value = e.detail || "#000000";
@@ -476,13 +469,14 @@
/*html*/
`
-
-
+
+
`
));
const BOOTSTRAP_ICONS = ["alarm", "archive", "arrow-left", "arrow-right", "bag", "bank", "basket", "bell", "bookmark", "box", "briefcase", "calendar", "camera", "cart", "chat", "check", "chevron-down", "chevron-left", "chevron-right", "chevron-up", "clock", "cloud", "code", "collection", "command", "cpu", "credit-card", "cup", "dash", "database", "display", "door-closed", "download", "droplet", "earbuds", "edit", "egg", "eject", "envelope", "eraser", "eye", "file", "filter", "flag", "folder", "gear", "gem", "gift", "graph-up", "grid", "hammer", "hand-thumbs-up", "heart", "house", "image", "inbox", "info-circle", "journal", "key", "laptop", "layers", "layout-text-sidebar-reverse", "lightbulb", "link", "list", "lock", "map", "mic", "moon", "mouse", "music-note", "newspaper", "palette", "paperclip", "pause", "pencil", "person", "phone", "pie-chart", "play", "plus", "printer", "puzzle", "question-circle", "reception-4", "record", "reply", "rss", "save", "search", "send", "server", "share", "shield", "shop", "shuffle", "skip-end", "skip-start", "slash", "sliders", "smartphone", "speaker", "speedometer", "spellcheck", "square", "star", "stickies", "stop", "stopwatch", "suit-heart", "sun", "table", "tag", "tags", "telephone", "terminal", "text-paragraph", "thermometer", "three-dots", "ticket", "tools", "trash", "trophy", "truck", "tv", "umbrella", "unlock", "upload", "vector-pen", "wallet", "watch", "wifi", "window", "wrench", "x", "zoom-in", "zoom-out", "activity", "at", "award", "backspace", "badge-3d", "badge-4k", "badge-8k", "badge-ad", "badge-ar", "badge-cc", "badge-hd", "badge-tm", "badge-vo", "badge-vr", "badge-wc", "bar-chart", "battery", "bicycle", "binoculars", "blockquote-left", "blockquote-right", "book", "bookshelf", "bootstrap", "border-all", "border-bottom", "border-center", "border-inner", "border-left", "border-middle", "border-outer", "border-right", "border-style", "border-top", "border-width", "bounding-box", "box-arrow-down", "box-arrow-in-down", "box-arrow-in-left", "box-arrow-in-right", "box-arrow-in-up", "box-arrow-left", "box-arrow-right", "box-arrow-up", "box-seam", "brightness-alt-high", "brightness-alt-low", "brightness-high", "brightness-low", "broadcast", "brush", "bucket", "bug", "building", "bullseye", "calculator", "calendar-check", "calendar-date", "calendar-day", "calendar-event", "calendar-minus", "calendar-month", "calendar-plus", "calendar-range", "calendar-week", "calendar-x", "calendar2", "calendar3", "calendar4", "camera-reels", "camera-video", "capslock", "card-checklist", "card-heading", "card-image", "card-list", "card-text", "caret-down", "caret-left", "caret-right", "caret-up", "cart-check", "cart-dash", "cart-plus", "cart-x", "cash", "cash-stack", "cast", "chat-dots", "chat-left", "chat-quote", "chat-right", "chat-square", "chat-text", "check-all", "check-circle", "check-square", "circle", "clipboard", "cloud-arrow-down", "cloud-arrow-up", "cloud-check", "cloud-download", "cloud-fog", "cloud-hail", "cloud-lightning", "cloud-minus", "cloud-moon", "cloud-plus", "cloud-rain", "cloud-slash", "cloud-snow", "cloud-sun", "cloud-upload", "clouds", "cloudy", "code-slash", "code-square", "collection-play", "columns", "columns-gap", "compass", "cone", "cone-striped", "controller", "credit-card-2-back", "credit-card-2-front", "crop", "cup-straw", "cursor", "dash-circle", "dash-square", "diagram-2", "diagram-3", "diamond", "dice-1", "dice-2", "dice-3", "dice-4", "dice-5", "dice-6", "disc", "discord", "distribute-horizontal", "distribute-vertical", "door-open", "dot", "droplet-half", "easel", "egg-fried", "emoji-angry", "emoji-dizzy", "emoji-expressionless", "emoji-frown", "emoji-heart-eyes", "emoji-laughing", "emoji-neutral", "emoji-smile", "emoji-sunglasses", "emoji-wink", "envelope-open", "exclamation", "exclamation-circle", "exclamation-diamond", "exclamation-octagon", "exclamation-square", "exclamation-triangle", "eye-slash", "eyedropper", "facebook", "file-arrow-down", "file-arrow-up", "file-binary", "file-break", "file-check", "file-code", "file-diff", "file-earmark", "file-excel", "file-image", "file-lock", "file-medical", "file-minus", "file-music", "file-pdf", "file-person", "file-play", "file-plus", "file-post", "file-ppt", "file-richtext", "file-slides", "file-spreadsheet", "file-text", "file-word", "file-zip", "files", "film", "filter-circle", "filter-left", "filter-right", "filter-square", "fingerprint", "flower1", "flower2", "flower3", "folder-check", "folder-minus", "folder-plus", "folder-symlink", "folder-x", "folder2-open", "fonts", "forward", "front", "fullscreen", "fullscreen-exit", "funnel", "gear-wide", "gender-female", "gender-male", "gender-trans", "geo", "geo-alt", "github", "globe", "google", "graph-down", "grid-1x2", "grid-3x2", "grid-3x3", "grip-horizontal", "grip-vertical", "hand-index", "hand-thumbs-down", "handbag", "hash", "headphones", "headset", "heart-half", "heptagon", "hourglass", "hourglass-bottom", "hourglass-split", "hourglass-top", "house-door", "hr", "hurricane", "image-alt", "images", "infinity", "input-cursor", "instagram", "intersect", "journal-album", "journal-arrow-down", "journal-arrow-up", "journal-bookmark", "journal-check", "journal-code", "journal-medical", "journal-minus", "journal-plus", "journal-richtext", "journal-text", "journal-x", "journals", "justify", "kanban", "keyboard", "ladder", "lamp", "layers-half", "layout-sidebar", "layout-split", "layout-three-columns", "life-preserver", "lightbulb-off", "lightning", "lightning-charge", "link-45deg", "linkedin", "list-check", "list-nested", "list-ol", "list-stars", "list-task", "list-ul", "mailbox", "markdown", "mask", "mastodon", "megaphone", "menu-app", "menu-button", "messenger", "mic-mute", "minecart", "minecart-loaded", "moisture", "mouse2", "mouse3", "music-note-beamed", "music-note-list", "music-player", "node-minus", "node-plus", "nut", "octagon", "option", "outlet", "paint-bucket", "patch-check", "patch-exclamation", "patch-minus", "patch-plus", "patch-question", "pause-btn", "pause-circle", "peace", "pen", "pencil-square", "pentagon", "person-badge", "person-bounding-box", "person-circle", "person-lines-fill", "phone-landscape", "phone-vibrate", "pie-chart-fill", "pin", "pin-angle", "pin-fill", "pin-map", "pip", "play-btn", "play-circle", "plug", "plus-circle", "plus-square", "power", "question", "question-diamond", "question-square", "rainbow", "receipt", "receipt-cutoff", "reception-0", "reception-1", "reception-2", "reception-3", "record-btn", "record-circle", "record2", "recycle", "reddit", "reply-all", "router", "rulers", "safe", "save2", "sd-card", "segmented-nav", "shield-check", "shield-exclamation", "shield-lock", "shield-shaded", "shield-slash", "shift", "signpost", "signpost-2", "signpost-split", "sim", "skip-backward", "skip-forward", "slack", "slash-circle", "slash-square", "smartwatch", "snow", "snow2", "snow3", "sort-alpha-down", "sort-alpha-up", "sort-numeric-down", "sort-numeric-up", "soundwave", "speedometer2", "square-half", "stack", "star-half", "stars", "stop-btn", "stop-circle", "suit-club", "suit-diamond", "suit-spade", "sunglasses", "sunrise", "sunset", "symmetry-horizontal", "symmetry-vertical", "tablet", "tablet-landscape", "telegram", "telephone-forward", "telephone-inbound", "telephone-outbound", "telephone-plus", "telephone-x", "text-center", "text-indent-left", "text-indent-right", "text-left", "text-right", "thermometer-half", "thermometer-high", "thermometer-low", "thermometer-snow", "thermometer-sun", "three-dots-vertical", "toggle-off", "toggle-on", "toggle2-off", "toggle2-on", "tornado", "translate", "trash2", "tree", "truck-flatbed", "tsunami", "type", "type-bold", "type-h1", "type-h2", "type-h3", "type-italic", "type-strikethrough", "type-underline", "ui-checks", "ui-checks-grid", "ui-radios", "ui-radios-grid", "union", "upc", "upc-scan", "view-list", "view-stacked", "vinyl", "voicemail", "volume-down", "volume-mute", "volume-off", "volume-up", "vr", "wallet2", "water", "whatsapp", "wifi-1", "wifi-2", "wifi-off", "wind", "window-dock", "window-sidebar", "x-circle", "x-diamond", "x-octagon", "x-square", "youtube"];
state.Component.register("IconPicker", (container) => {
+ container._thisObj = container;
container.state = state.NewState({ value: "", search: "", open: false });
container.addEventListener("bind", (e) => {
container.state.value = e.detail || "";
@@ -629,9 +623,7 @@
const scrollTop = container.scrollTop;
let loopCount = 0;
for (let i = 0; i < size; i++) {
- if (++loopCount > size * 2) {
- throw new Error(`VirtualScroll.calc infinite loop detected at i=${i}, status=${status}, size=${size}, groupItemCount=${groupItemCount}`);
- }
+ if (++loopCount > size * 2) throw new Error("VirtualScroll infinite loop");
if (status === 0) {
const gh = groupHeights.get(i);
if (gh && prev + gh <= scrollTop && i + groupItemCount < size) {
@@ -656,9 +648,7 @@
if (gh) {
post += gh;
i += groupItemCount - 1;
- } else {
- post += itemHeights.get(i);
- }
+ } else post += itemHeights.get(i);
}
}
const finalPrevHeight = Math.max(0, prev - padTop - topMargin - rowGap - (listStartIndex > 0 ? rowGap : 0));
@@ -686,6 +676,7 @@
itemicon: "file"
};
container.collapsed = state.NewState({});
+ container.state.renderedList = [];
const updateFlatList = () => {
state.Util.updateDefaults(container, defaultSets);
const list = container.state.list || [], flatList = [];
@@ -715,27 +706,21 @@
});
traverse(childrenMap[""] || [], 0, []);
} else list.forEach((item) => flatList.push({ type: "item", ...item }));
- container.state._flatList = flatList;
+ container.state.flatList = flatList;
};
container.state.__watch("list", updateFlatList);
const vs = container.fast ? VirtualScroll() : null;
- container.state._renderedList = [];
let refreshing = false;
container.refresh = () => {
if (!container.fast || refreshing) return;
refreshing = true;
try {
- const res = vs.calc(container, container.state._flatList);
+ const res = vs.calc(container, container.state.flatList);
if (res) {
if (padTopEl) padTopEl.style.height = `${res.prevHeight}px`;
if (padBottomEl) padBottomEl.style.height = `${res.postHeight}px`;
- if (container.state._listStartIndex !== res.listStartIndex) {
- container.state._listStartIndex = res.listStartIndex;
- }
- const cur = container.state._renderedList || [];
- if (cur.length !== res.renderedList.length || cur[0] !== res.renderedList[0] || cur[cur.length - 1] !== res.renderedList[res.renderedList.length - 1]) {
- container.state._renderedList = res.renderedList;
- }
+ container.state.listStartIndex = res.listStartIndex;
+ container.state.renderedList = res.renderedList;
}
} finally {
setTimeout(() => {
@@ -744,22 +729,22 @@
}
};
container.onItemUpdate = (index2, node) => {
- if (container.fast) vs.update(index2 + (container.state._listStartIndex || 0), node);
+ if (container.fast) vs.update(index2 + (container.state.listStartIndex || 0), node);
};
- container.state.__watch("_flatList", (flatList) => {
+ container.state.__watch("flatList", (flatList) => {
if (container.fast) {
if (padTopEl) padTopEl.style.height = "0px";
if (padBottomEl) padBottomEl.style.height = "0px";
- container.state._listStartIndex = 0;
- container.state._renderedList = vs.reset(flatList, container) || [];
+ container.state.listStartIndex = 0;
+ container.state.renderedList = vs.reset(flatList, container) || [];
setTimeout(() => {
- if (container.state._flatList === flatList) vs.init(flatList, container.refresh);
+ if (container.state.flatList === flatList) vs.init(flatList, container.refresh);
});
- } else container.state._renderedList = flatList;
+ } else container.state.renderedList = flatList;
});
container.selectItem = (item, index2) => {
if (container.hasAttribute("auto-select")) container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield];
- container.dispatchEvent(new CustomEvent("itemclick", { bubbles: false, detail: { item, index: index2 + (container.fast ? container.state._listStartIndex || 0 : 0) } }));
+ container.dispatchEvent(new CustomEvent("itemclick", { bubbles: false, detail: { item, index: index2 + (container.fast ? container.state.listStartIndex || 0 : 0) } }));
};
container.selectGroup = (item, index2) => {
if (container.hasAttribute("auto-select-group")) container.state.selectedGroup = container.state.selectedGroup === item[container.groupidfield] ? null : item[container.groupidfield];
@@ -777,14 +762,14 @@
`
-
-
+
+
-
+
@@ -802,7 +787,7 @@
-
+
diff --git a/test/lib/state.js b/test/lib/state.js
index c2d2c05..d217def 100644
--- a/test/lib/state.js
+++ b/test/lib/state.js
@@ -3,12 +3,12 @@
})(this, function(exports2) {
"use strict";
var _a;
- let _activeBinding = null;
- let _noWriteBack = null;
- const setActiveBinding = (val) => _activeBinding = val;
- const setNoWriteBack = (val) => _noWriteBack = val;
+ let __activeBinding = null;
+ let __noWriteBack = null;
+ const _setActiveBinding = (val) => __activeBinding = val;
+ const _setNoWriteBack = (val) => __noWriteBack = val;
const _notifiers = /* @__PURE__ */ new Set();
- const onNotifyUpdate = (fn) => _notifiers.add(fn);
+ const _onNotifyUpdate = (fn) => _notifiers.add(fn);
function NewState(defaults = {}, getter = null, setter = null) {
const _defaults = {};
const _stateMappings = /* @__PURE__ */ new Map();
@@ -30,11 +30,11 @@
if (key === "__watch") return _watchFunc;
if (key === "__unwatch") return _unwatchFunc;
if (key === "__isProxy") return true;
- if (_activeBinding) {
+ if (__activeBinding) {
if (!_stateMappings.has(key)) _stateMappings.set(key, /* @__PURE__ */ new Set());
- _stateMappings.get(key).add(_activeBinding);
- if (!_activeBinding.node._states) _activeBinding.node._states = /* @__PURE__ */ new Set();
- _activeBinding.node._states.add(_stateMappings);
+ _stateMappings.get(key).add(__activeBinding);
+ if (!__activeBinding.node._states) __activeBinding.node._states = /* @__PURE__ */ new Set();
+ __activeBinding.node._states.add(_stateMappings);
}
return __getter(key);
},
@@ -61,7 +61,7 @@
bindings.delete(binding);
continue;
}
- if (_noWriteBack !== binding.node) {
+ if (__noWriteBack !== binding.node) {
_notifiers.forEach((fn) => fn(binding));
}
}
@@ -111,7 +111,9 @@
});
}
to.classList.add(...from.classList);
- Array.from(from.childNodes).forEach((child) => to.appendChild(child));
+ const target = to.tagName === "TEMPLATE" ? to.content : to;
+ const sourceNodes = from.tagName === "TEMPLATE" ? from.content.childNodes : from.childNodes;
+ Array.from(sourceNodes).forEach((child) => target.appendChild(child));
if (from.tagName && Component.exists(from.tagName)) _makeComponent(from.tagName, to, scanObj, exists);
}
function _makeComponent(name, node, scanObj, exists = {}) {
@@ -206,7 +208,7 @@
};
}
}
- onNotifyUpdate((binding) => _updateBinding(binding));
+ _onNotifyUpdate((binding) => _updateBinding(binding));
function _clearRenderedNodes(node) {
if (node._renderedNodes) node._renderedNodes.forEach((nodes) => nodes.forEach((child) => {
child.remove();
@@ -216,7 +218,7 @@
function _updateBinding(binding) {
const node = binding.node;
if (!node.isConnected && node.tagName !== "TEMPLATE") return;
- setActiveBinding(binding);
+ _setActiveBinding(binding);
let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl;
if (binding.exp === 2 && typeof result === "string") {
try {
@@ -224,7 +226,7 @@
} catch (e) {
}
}
- setActiveBinding(null);
+ _setActiveBinding(null);
if (binding.prop) {
const prop = binding.prop;
let o = node;
@@ -235,10 +237,9 @@
if (typeof o !== "object") break;
}
if (typeof o === "object" && o !== null) {
- const resultIsObject = typeof result === "object" && result != null && !Array.isArray(result);
const lk = prop[prop.length - 1];
if (lk) {
- if (resultIsObject && o[lk] == null) o[lk] = {};
+ if (typeof result === "object" && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {};
const lo = o[lk];
if (typeof lo === "object" && lo != null && lo.__watch) Object.assign(lo, result);
else {
@@ -419,12 +420,12 @@
if (realAttrName === "bind") {
node.addEventListener(["textarea", "text", "password"].includes(node.type || "text") || node.isContentEditable ? "input" : "change", (e) => {
let newVal = node.isContentEditable ? e.target.innerHTML : node.type === "checkbox" ? e.target.checked : e.target.files || e.target.value || e.detail;
- setNoWriteBack(node);
+ _setNoWriteBack(node);
setDisableRunCodeError(true);
if (node.type === "checkbox" && node._checkboxMultiMode) _runCode(`!!checked ? (!${tpl}.includes(val) && ${tpl}.push(val)) : (index = ${tpl}.indexOf(val), index > -1 && ${tpl}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
else _runCode(`${tpl} = val`, { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {});
setDisableRunCodeError(false);
- setNoWriteBack(null);
+ _setNoWriteBack(null);
});
} else if (realAttrName === "text" && !tpl) {
tpl = node.textContent;
@@ -441,6 +442,13 @@
if (node._thisObj) scanObj.thisObj = node._thisObj;
};
const _scanTree = (node, scanObj = {}) => {
+ if (node.nodeType === 3) {
+ if (node._stTranslated) return;
+ const translated = _translate(node.textContent);
+ if (translated !== node.textContent) node.textContent = translated;
+ node._stTranslated = true;
+ return;
+ }
if (node.nodeType !== 1) return;
if (!node._stTranslated) {
Array.from(node.attributes).forEach((attr) => {
@@ -451,6 +459,18 @@
});
node._stTranslated = true;
}
+ if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) {
+ const template = document.createElement("TEMPLATE");
+ const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name));
+ attrs.forEach((attr) => {
+ template.setAttribute(attr.name, attr.value);
+ node.removeAttribute(attr.name);
+ });
+ node.parentNode.insertBefore(template, node);
+ template.content.appendChild(node);
+ template._ref = node._ref;
+ return;
+ }
if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) {
const template = document.createElement("TEMPLATE");
const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name));
@@ -508,7 +528,7 @@
});
node.childNodes && node.childNodes.forEach((child) => _unbindTree(child));
};
- const ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = _scanTree;
+ const _unsafeRefreshState = _scanTree;
const Util = {
clone: window.structuredClone || ((obj) => JSON.parse(JSON.stringify(obj))),
base64: (str) => btoa(String.fromCharCode(...new TextEncoder().encode(str))),
@@ -603,18 +623,14 @@
Component,
$,
$$,
- ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios,
+ RefreshState: _unsafeRefreshState,
SetTranslator,
_scanTree,
_unbindTree,
Util,
Hash,
LocalStorage,
- State,
- _runCode,
- _returnCode,
- onNotifyUpdate,
- setActiveBinding
+ State
};
if (typeof window !== "undefined") {
window.ApigoState = ApigoState;
@@ -645,10 +661,10 @@
exports2.Hash = Hash;
exports2.LocalStorage = LocalStorage;
exports2.NewState = NewState;
+ exports2.RefreshState = _unsafeRefreshState;
exports2.SetTranslator = SetTranslator;
exports2.State = State;
exports2.Util = Util;
- exports2.____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios = ____RefreshState_Internal_Force_Full_Scan_Only_In_Extreme_Performance_Scenarios;
exports2._scanTree = _scanTree;
exports2._unbindTree = _unbindTree;
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
diff --git a/test/list_test.html b/test/list_test.html
new file mode 100644
index 0000000..aa461e5
--- /dev/null
+++ b/test/list_test.html
@@ -0,0 +1,100 @@
+
+
+
+
+ List Mega Test (Virtual Scroll Stress Test)
+
+
+
+
+
+
+
+
+
1. Standard List (Normal)
+
+
+
+
2. Group List (Mode: Group)
+
+
+
+
3. Tree List (Mode: Tree + Collapsible)
+
+
+
+
4. FAST Virtual List (10,000 Items + Dynamic Height)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/mega_verify.spec.js b/test/mega_verify.spec.js
new file mode 100644
index 0000000..7c312e1
--- /dev/null
+++ b/test/mega_verify.spec.js
@@ -0,0 +1,59 @@
+import { test, expect } from '@playwright/test';
+
+test('Empirical Mega Verification', async ({ page }) => {
+ page.on('console', msg => console.log('BROWSER:', msg.text()));
+
+ // 1. Verify AutoForm
+ console.log('--- Verifying AutoForm Mega ---');
+ await page.goto('http://localhost:5173/test/form_test.html');
+ await page.waitForTimeout(3000);
+
+ const checkForm = async (id) => {
+ return await page.evaluate((fid) => {
+ const form = document.getElementById(fid);
+ const inputs = form.querySelectorAll('input, select, textarea');
+ const labels = form.querySelectorAll('label');
+ return {
+ id: fid,
+ inputCount: inputs.length,
+ labelCount: labels.length,
+ html: form.innerHTML.substring(0, 100)
+ };
+ }, id);
+ };
+
+ const vResult = await checkForm('formV');
+ const hResult = await checkForm('formH');
+ const iResult = await checkForm('formI');
+
+ console.log('Form results:', { vResult, hResult, iResult });
+ expect(vResult.inputCount).toBeGreaterThan(5);
+ expect(hResult.inputCount).toBeGreaterThan(5);
+
+ // 2. Verify List
+ console.log('--- Verifying List Mega ---');
+ await page.goto('http://localhost:5173/test/list_test.html');
+ await page.waitForTimeout(3000);
+
+ const checkList = async (id) => {
+ return await page.evaluate((lid) => {
+ const list = document.getElementById(lid);
+ const items = list.querySelectorAll('.list-group-item');
+ return {
+ id: lid,
+ itemCount: items.length,
+ labels: Array.from(items).map(i => i.textContent.trim())
+ };
+ }, id);
+ };
+
+ const stdResult = await checkList('listStd');
+ const grpResult = await checkList('listGrp');
+ const treeResult = await checkList('listTree');
+
+ console.log('List results:', { stdResult, grpResult, treeResult });
+
+ expect(stdResult.itemCount).toBeGreaterThan(0);
+ expect(grpResult.itemCount).toBeGreaterThan(2);
+ expect(treeResult.itemCount).toBeGreaterThan(0);
+});
diff --git a/test/verify_stress.spec.js b/test/verify_stress.spec.js
new file mode 100644
index 0000000..3f26777
--- /dev/null
+++ b/test/verify_stress.spec.js
@@ -0,0 +1,34 @@
+import { test, expect } from '@playwright/test';
+
+test('Stress test: Virtual List should handle 10,000 items with dynamic height', async ({ page }) => {
+ test.setTimeout(60000);
+ page.on('console', msg => console.log('BROWSER LOG:', msg.text()));
+
+ await page.goto('http://localhost:5174/test/list_test.html');
+
+ const listFast = page.locator('#listFast');
+ await expect(listFast).toBeVisible();
+ await page.waitForFunction(() => document.querySelectorAll('#listFast .list-group-item').length > 0);
+
+ // Check initial state
+ const scrollTop = await listFast.evaluate(e => e.scrollTop);
+ const scrollHeight = await listFast.evaluate(e => e.scrollHeight);
+ const clientHeight = await listFast.evaluate(e => e.clientHeight);
+ console.log(`Initial: scrollTop=${scrollTop}, scrollHeight=${scrollHeight}, clientHeight=${clientHeight}`);
+
+ // Attach event listener to see if scroll fires
+ await listFast.evaluate(e => {
+ e.addEventListener('scroll', () => console.log('SCROLL EVENT FIRED! new scrollTop:', e.scrollTop));
+ });
+
+ // Scroll to the very end
+ await listFast.evaluate(e => e.scrollTop = e.scrollHeight);
+ await page.waitForTimeout(1000);
+
+ const finalScrollTop = await listFast.evaluate(e => e.scrollTop);
+ console.log(`Final: scrollTop=${finalScrollTop}`);
+
+ const lastItemText = await listFast.locator('.list-group-item').last().textContent();
+ console.log('Last rendered item text:', lastItemText);
+ expect(lastItemText).toContain('Virtual Item 10000');
+});
diff --git a/test/verify_style.spec.js b/test/verify_style.spec.js
new file mode 100644
index 0000000..55a6b81
--- /dev/null
+++ b/test/verify_style.spec.js
@@ -0,0 +1,20 @@
+import { test, expect } from '@playwright/test';
+
+test('Check styles and globals', async ({ page }) => {
+ await page.goto('http://localhost:5174/test/form_test.html');
+
+ // Check if auto-grid-form is grid (horizontal mode)
+ const formH = page.locator('#formH form');
+ const display = await formH.evaluate(el => window.getComputedStyle(el).display);
+ console.log('formH display:', display);
+ expect(display).toBe('grid');
+
+ // Get color picker input to check height
+ const colorInput = page.locator('#formH input[type="color"]');
+ if (await colorInput.count() > 0) {
+ const height = await colorInput.evaluate(el => window.getComputedStyle(el).height);
+ console.log('colorInput height:', height);
+ // It shouldn't be very small (like 0 or 2px)
+ expect(parseInt(height)).toBeGreaterThan(20);
+ }
+});
diff --git a/vite.config.js b/vite.config.js
index 8ea1d2d..472a664 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -19,7 +19,7 @@ export default defineConfig({
lib: {
entry: resolve(__dirname, 'src/index.js'),
name: 'ApigoBase',
- formats: ['umd', 'es']
+ formats: ['umd']
},
rollupOptions: {
external: ['@apigo.cc/state', '@apigo.cc/bootstrap'],
@@ -42,15 +42,6 @@ export default defineConfig({
'@apigo.cc/bootstrap': 'bootstrap'
},
plugins: [terser()]
- },
- {
- format: 'es',
- entryFileNames: 'base.mjs'
- },
- {
- format: 'es',
- entryFileNames: 'base.min.mjs',
- plugins: [terser()]
}
]
},