import { Component, NewState, Util, $, Hash, RefreshState } from "@web/state";
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 || "body", 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 = [];
container.vertical = container.hasAttribute("vertical");
container.request = { method: "POST" };
container.response = {};
container.result = null;
container.data = NewState(container.data || {});
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 _pendingAutoFormComponents = [];
const AutoForm = {
register: (name) => {
if (typeof document !== "undefined") {
if (document.readyState !== "loading" && Component.getTemplate("AutoForm")) AutoForm._addAutoFormComponent(name);
else _pendingAutoFormComponents.push(name);
}
},
_addAutoFormComponent: (name) => {
var _a;
const template = Component.getTemplate("AutoForm");
if (template) {
(_a = $(template.content, "[control-wrapper]")) == null ? void 0 : _a.appendChild(Util.makeDom(`<${name} $if="item.type.toUpperCase() === '${name.toUpperCase()}'" $name="item.name" $.="item.setting" $bind="this.data[item.name]">${name}>`));
} else if (document.readyState !== "loading") {
console.error("AutoForm template not found during registration of", name);
}
}
};
if (typeof document !== "undefined") {
const initAutoForm = () => {
_pendingAutoFormComponents.forEach((name) => AutoForm._addAutoFormComponent(name));
_pendingAutoFormComponents.length = 0;
};
if (document.readyState !== "loading") setTimeout(initAutoForm, 100);
else document.addEventListener("DOMContentLoaded", () => setTimeout(initAutoForm, 100), true);
}
Component.register("TagsInput", (container) => {
container.addEventListener("bind", (e) => {
container.state.tags = e.detail || [];
});
}, Util.makeDom(
/*html*/
`
{
if (event.isComposing) return;
if (["Enter", ",", " "].includes(event.key)) {
event.preventDefault();
const v = thisNode.value.trim();
if (v && !(void 0).state.tags.includes(v)) {
(void 0).state.tags.push(v);
(void 0).state.tags = (void 0).state.tags;
(void 0).dispatchEvent(new CustomEvent("change", { bubbles: false, detail: (void 0).state.tags }));
}
thisNode.value = "";
}
})}">
`
), Util.makeDom(
/*html*/
``
));
AutoForm.register("TagsInput");
const FastListComponent = Component.register("FastList", (container) => {
const itemHeights = /* @__PURE__ */ new Map();
const groupHeights = /* @__PURE__ */ new Map();
let groupItemCount = 1;
container.state.renderedList = [];
const avg = Util.newAvg();
let containerPaddingTop = 0;
let containerRowGap = 0;
let topMargin = 0;
let listStartIndex = 0;
let visibleStartIndex = 0;
let visibleCount = 10;
let listInited = false;
container.onItemUpdate = (index2, node) => {
const absoluteIndex = index2 + listStartIndex;
const oldHeight = itemHeights.get(absoluteIndex);
const style = window.getComputedStyle(node);
const marginTop = parseFloat(style.marginTop) || 0;
const marginBottom = parseFloat(style.marginBottom) || 0;
const newHeight = node.offsetHeight + marginTop + marginBottom + containerRowGap;
if (absoluteIndex === 0 && !topMargin) topMargin = marginTop;
if (newHeight !== oldHeight) {
itemHeights.set(absoluteIndex, newHeight);
avg.add(newHeight);
const offset = newHeight - oldHeight || 0;
const groupIndex = absoluteIndex - absoluteIndex % groupItemCount;
if (groupHeights.has(groupIndex)) groupHeights.set(groupIndex, groupHeights.get(groupIndex) + offset);
}
};
container.refresh = () => {
if (!listInited) return;
visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32));
let list = [];
const fullList = container.state.list;
const fullSize = fullList.length;
let i = 0;
let prev = containerPaddingTop + topMargin + containerRowGap;
let post = 0;
let starus = 0;
for (i = 0; i < fullSize; i++) {
if (starus === 0) {
const gh = groupHeights.get(i);
if (gh && prev + gh < container.scrollTop) {
prev += gh;
i += Math.min(groupItemCount, fullSize - i) - 1;
} else {
const ih = itemHeights.get(i);
if (prev + ih < container.scrollTop) {
prev += ih;
} else {
starus = 1;
visibleStartIndex = Math.max(0, i);
listStartIndex = Math.max(0, visibleStartIndex - visibleCount);
const listEndIndex = Math.min(listStartIndex + visibleCount * 3, fullSize);
i = listEndIndex - 1;
list = fullList.slice(listStartIndex, listEndIndex);
for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j);
}
}
} else if (starus === 1) {
const gh = groupHeights.get(i);
if (gh) {
post += gh;
i += groupItemCount - 1;
} else {
post += itemHeights.get(i);
}
}
}
container.state.prevHeight = prev - containerPaddingTop - topMargin - containerRowGap;
container.state.postHeight = post;
container.state.renderedList = list;
};
container.state.__watch("list", (list) => {
console.log("FastList list bound, size:", list == null ? void 0 : list.length, container.id);
listInited = false;
itemHeights.clear();
groupHeights.clear();
avg.clear();
if (!(list == null ? void 0 : list.length)) return;
const listSize = list.length || 0;
groupItemCount = Math.ceil(Math.sqrt(listSize)) || 10;
const style = window.getComputedStyle(container);
containerPaddingTop = parseFloat(style.paddingTop);
containerRowGap = parseFloat(style.rowGap) || 0;
container.state.renderedList = list.slice(0, Math.min(30, listSize)) || [];
setTimeout(() => {
container.state.list.forEach((item, index2) => itemHeights.set(index2, avg.get()));
for (let i = 0; i < listSize; i += groupItemCount) groupHeights.set(i, Math.min(groupItemCount, listSize - i) * avg.get());
listInited = true;
console.log("FastList inited, calling refresh", container.id);
container.refresh();
});
});
}, Util.makeDom(
/*html*/
`
0" $style="height:\${this.state.prevHeight}px;" class="flex-shrink-0">
0" $style="height:\${this.state.postHeight}px;" class="flex-shrink-0">
`
));
Component.attachSelectFeature = (container, eventName = "itemclick", selectActionName = "selectItem", activeActionName = "itemActiveTag", selectedItemKey = "selectedItem", idfieldKey = "idfield", selectableAttr = "auto-select") => {
const selectable = selectableAttr && container.hasAttribute(selectableAttr);
container[selectActionName] = (item, index2) => {
if (selectable) container.state[selectedItemKey] = container.state[selectedItemKey] === item[container[idfieldKey]] ? null : item[container[idfieldKey]];
container.dispatchEvent(new CustomEvent(eventName, { bubbles: false, detail: !selectable || container.state[selectedItemKey] ? { item, index: index2 } : { item: {}, index: null } }));
};
container[activeActionName] = (item) => container.state[selectedItemKey] !== null && container.state[selectedItemKey] === item[container[idfieldKey]] ? "active" : "";
};
Component.register("List", (container) => {
Component.attachSelectFeature(container);
const defaultSets = { idfield: "id", labelfield: "label", summaryfield: "summary" };
container.state.__watch("list", (oldList) => {
Util.updateDefaults(container, defaultSets);
});
}, Util.makeDom(
/*html*/
`
\${item[this.labelfield]}
`
));
Component.register("GroupedList", (container) => {
Component.getSetupFunction("List")(container);
container._selectItem = container.selectItem;
container.selectItem = (item, index2) => {
container.state.selectedGroup = null;
container._selectItem(item, index2);
};
Component.attachSelectFeature(container, "groupclick", "selectGroup", "groupActiveTag", "selectedGroup", "groupidfield", container.hasAttribute("auto-select-group") || container.hasAttribute("auto-select") && "group-selectable");
container._selectGroup = container.selectGroup;
container.selectGroup = (item, index2) => {
container.state.selectedItem = null;
container._selectGroup(item, index2);
};
const defaultSets = { typefield: "type", groupidfield: "id", grouplabelfield: "label", groupsummaryfield: "summary", groupfield: "group" };
container.state.__watch("list", (oldList) => {
Util.updateDefaults(container, defaultSets);
const newList = [];
(container.state.groups || []).forEach((group) => {
newList.push({ [container.typefield]: "group", ...group });
if (oldList) oldList.forEach((item) => {
if (item[container.groupfield] === group[container.groupidfield]) {
newList.push({ [container.typefield]: "item", ...item });
}
});
});
return newList;
});
}, Util.makeDom(
/*html*/
`
\${item[this.grouplabelfield]}
\${item[this.labelfield]}
`
));
Component.register("FastGroupedList", (container) => {
Component.getSetupFunction("List")(container);
Component.getSetupFunction("GroupedList")(container);
}, Util.makeDom(
/*html*/
`
\${item[this.grouplabelfield]}
\${item[this.labelfield]}
`
));
Component.register("Tree", (container) => {
const defaultSets = { parentfield: "parent", groupicon: "folder", itemicon: "file" };
container.state.__watch("list", () => {
Util.updateDefaults(container, defaultSets);
const childrenMap = {};
(container.state.list || []).forEach((item) => {
var _a;
return (childrenMap[_a = item[container.parentfield] || ""] ?? (childrenMap[_a] = [])).push(item);
});
const newList = [];
const traverse = (list, level) => list.forEach((item) => {
var _a;
const id = item[container.idfield];
const hasChildren = !!((_a = childrenMap[id]) == null ? void 0 : _a.length);
newList.push({ ...item, _level: level, _hasChildren: hasChildren });
if (hasChildren) traverse(childrenMap[id], level + 1);
});
traverse(childrenMap[""] || [], 0);
return newList;
});
}, Util.makeDom(
/*html*/
`
`
));
const FastTreeComponent = Component.register("FastTree", (container) => {
Component.getSetupFunction("List")(container);
Component.getSetupFunction("Tree")(container);
}, Util.makeDom(
/*html*/
`
`
));
Component.register("CollapseTree", (container) => {
Component.attachSelectFeature(container);
const defaultSets = { idfield: "id", parentfield: "parent", labelfield: "label", summaryfield: "summary" };
container.collapsed = NewState({});
container.state.__watch("list", () => {
Util.updateDefaults(container, defaultSets);
const childrenMap = {};
(container.state.list || []).forEach((item) => {
var _a;
return (childrenMap[_a = item[container.parentfield] || ""] ?? (childrenMap[_a] = [])).push(item);
});
const newList = [];
const traverse = (list, level, parents) => list.forEach((item) => {
var _a;
const id = item[container.idfield];
const hasChildren = !!((_a = childrenMap[id]) == null ? void 0 : _a.length);
newList.push({ ...item, _level: level, _hasChildren: hasChildren, _parents: parents });
if (hasChildren) traverse(childrenMap[id], level + 1, [...parents, id]);
});
traverse(childrenMap[""] || [], 0, []);
return newList;
});
}, Util.makeDom(
/*html*/
`
this.collapsed[cur])" $class="list-group-item list-group-item-action d-flex ps-2 align-items-center \${this.itemActiveTag?.(item)}" $onclick="this.selectItem(item,index)">
`
));
Component.register("Nav", (container) => {
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;
}
};
globalThis.MouseMover = MouseMover;
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) => {
const isVertical = container.hasAttribute("vertical");
const min = parseInt(container.getAttribute("min")) || 10;
const max = parseInt(container.getAttribute("max")) || 1e3;
const target = container.target || container.previousElementSibling;
const getSize = (startSize, w, h) => {
const newSize = startSize + (isVertical ? h : w);
return newSize < min ? min : newSize > max ? max : newSize;
};
container.addEventListener("mousedown", (event) => {
const startSize = isVertical ? target.offsetHeight : target.offsetWidth;
MouseMover.start(event, {
onmousemove: ({ w, h }) => target.style[isVertical ? "height" : "width"] = getSize(startSize, w, h) + "px",
onmouseup: ({ w, h }) => container.dispatchEvent(new CustomEvent("resize", { detail: { oldSize: startSize, newSize: getSize(startSize, w, h) }, bubbles: false }))
});
});
}, Util.makeDom(
/*html*/
`
`
));
const State = NewState({ exitBlocks: 0 });
globalThis.State = State;
if (typeof window !== "undefined") {
window.addEventListener("beforeunload", (event) => {
if (State.exitBlocks > 0) event.preventDefault();
});
}
globalThis.HTTP = HTTP;
globalThis.UI = UI$1;
globalThis.AutoForm = AutoForm;
globalThis.MouseMover = MouseMover;
if (typeof document !== "undefined") {
const doRefresh = () => {
console.log("Base project triggering RefreshState");
RefreshState(document.documentElement);
};
if (document.readyState !== "loading") doRefresh();
else document.addEventListener("DOMContentLoaded", doRefresh, true);
}
export {
APIComponent,
AutoForm,
FastListComponent,
FastTreeComponent,
HTTP,
MouseMover,
State,
UI$1 as UI
};