base/dist/base.js

579 lines
33 KiB
JavaScript
Raw Permalink Normal View History

(function() {
"use strict";
(function(global) {
const { Component, Util, $ } = global;
const UI = {};
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*/
`
<div class="modal fade" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div $class="modal-content border-\${this.state?.type || 'primary'} border-2 shadow-lg">
<div slot-id="header" class="modal-header py-2 px-3 bg-light">
<h6 $class="modal-title fw-bold text-\${this.state?.type || 'primary'}" $text="this.state?.title"></h6>
<button type="button" class="btn btn-link ms-2 bi bi-x-lg link-reset p-0" style="color:inherit; text-decoration:none" data-bs-dismiss="modal"></button>
</div>
<div slot-id="body" class="modal-body p-3"></div>
<div slot-id="footer" class="modal-footer py-2 px-3 bg-light"></div>
</div>
</div>
</div>
`
));
Component.register("Dialog", Component.getSetupFunction("Modal"), Util.makeDom(
/*html*/
`
<div class="modal fade" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div $class="modal-content border-\${this.state?.type || 'primary'} border-2 shadow-lg">
<template $if="this.state?.title">
<div $class="modal-header py-2 px-3 bg-light fw-bold text-\${this.state?.type || 'primary'}" $text="this.state?.title"></div>
</template>
<div slot-id="body" class="modal-body p-4"><div $html="this.state?.message"></div></div>
<div class="modal-footer py-2 px-3 bg-light">
<template $each="this.state?.buttons || ['{#Close#}']">
<button type="button" $class="btn btn-sm px-3 btn-\${index === (this.state?.buttons || []).length - 1 ? (this.state?.type && this.state?.type !== 'body' ? this.state?.type : 'primary') : 'outline-secondary border'}" $onclick="this.result=index+1;this.hide()" $text="\${item}"></button>
</template>
</div>
</div>
</div>
</div>
`
));
let _dialogCount = 0;
UI.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.alert = function(message, options = {}) {
return UI.showDialog({ message, ...options });
};
UI.confirm = function(message, options = {}) {
return new Promise((resolve) => UI.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*/
`
<div class="toast align-items-center border-0 m-1">
2026-05-17 16:59:44 +08:00
<div $class="toast-body rounded p-3 text-bg-\${this.state?.type}">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<span style="white-space:pre-wrap" class="fs-6" $text="this.state?.message"></span>
<template $if="this.state?.left !== undefined">
<span class="small text-dim ms-2" $text="\${this.state?.left}s"></span>
</template>
</div>
<button type="button" class="btn btn-link ms-3 bi bi-x-lg link-reset" style="color:inherit" data-bs-dismiss="toast"></button>
</div>
<div class="d-flex justify-content-end gap-3">
<template $each="this.state?.buttons || ['{#Close#}']">
<button type="button" $class="btn btn-sm btn-\${this.state?.type} mt-2" data-bs-dismiss="toast" $onclick="this.result=index+1" $text="item"></button>
</template>
</div>
</div>
</div>
`
), Util.makeDom(
/*html*/
`<div toast-container="default" class="position-fixed bottom-0 end-0 overflow-auto" style="z-index:3000;max-height:80%"></div>`
));
UI.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.toastConfirm = function(message, options = {}) {
return new Promise((resolve) => UI.toast(message, { buttons: ["{#Confirm#}"], ...options }).then((index2) => resolve(index2 === 1)).catch(() => resolve(false)));
};
global.UI = UI;
})(globalThis);
(function(global) {
const { Component } = global;
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 }) => {
method = method.toUpperCase();
const opt = { method, headers: { "Content-Type": "application/json", ...headers } };
if (data !== void 0) opt.body = JSON.stringify(data);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
opt.signal = controller.signal;
try {
const response = await fetch(url, opt);
clearTimeout(timer);
const result = responseType === "blob" ? await response.blob() : responseType === "text" ? await response.text() : await response.json();
return { ok: response.ok, status: response.status, result };
} catch (err) {
clearTimeout(timer);
return { ok: false, status: 0, error: err.message };
}
}
};
Component.register("API", (container) => {
if (!container.state.request) container.state.request = { url: "", method: "GET", data: {}, noui: false };
if (!container.state.response) container.state.response = { ok: false, status: 0, result: null, loading: false };
container.do = async (req) => {
var _a;
const opt = { ...container.state.request, ...req };
container.state.response.loading = true;
const resp = await HTTP.request(opt);
Object.assign(container.state.response, resp, { loading: false });
if (!resp.ok) {
if (!opt.noui && ((_a = global.UI) == null ? void 0 : _a.toast)) global.UI.toast(resp.error || "Request failed", { type: "danger" });
}
container.dispatchEvent(new CustomEvent("success", { bubbles: false, detail: resp.result }));
return resp;
};
let _autoTimer = null;
container.state.request.__watch(null, () => {
if (!container.hasAttribute("auto") || !container.state.request.url) return;
if (_autoTimer) return;
_autoTimer = Promise.resolve().then(() => {
container.do();
_autoTimer = null;
});
});
});
global.HTTP = HTTP;
})(globalThis);
(function(global) {
const { Component, NewState, Util, $ } = global;
const AutoForm = {
customTypes: [],
register: (name, typeName) => {
const type = typeName || name;
if (!AutoForm.customTypes.includes(type)) AutoForm.customTypes.push(type);
}
};
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.addEventListener("submit", async (event) => {
var _a, _b, _c, _d;
event.preventDefault();
if (!container.form.reportValidity()) return (_b = (_a = global.UI) == null ? void 0 : _a.toast) == null ? void 0 : _b.call(_a, "{#verify failed#}", { type: "danger" });
container.state.formState = "submitting";
const detail = JSON.parse(JSON.stringify(container.data));
const customEvent = new CustomEvent("submit", { bubbles: false, cancelable: true, detail });
container.dispatchEvent(customEvent);
if (customEvent.defaultPrevented) return;
try {
if (container.state.action) {
const resp = await global.HTTP.request({ url: container.state.action, method: "POST", data: detail });
if (!resp.ok) throw new Error(resp.error);
}
container.state.formState = "success";
if ((_c = global.UI) == null ? void 0 : _c.toast) global.UI.toast("{#submit success#}", { type: "success" });
} catch (err) {
container.state.formState = "error";
if ((_d = global.UI) == null ? void 0 : _d.toast) global.UI.toast(err.message, { type: "danger" });
}
});
}, Util.makeDom(
/*html*/
`
<form class="auto-form-root" $onsubmit="this.dispatchEvent(new Event('submit'))">
<div $class="auto-grid-form \${this.state?.grid ? 'row g-3' : ''}">
<template $each="this.state?.schema" as="field">
<div $class="\${this.state?.grid ? 'col-md-' + (field.col || 12) : 'mb-3'}">
<label $if="field.label" class="form-label small fw-bold text-muted mb-1" $text="field.label"></label>
<div class="field-container">
<template $if="!field.type || field.type === 'text'">
<input type="text" class="form-control" $bind="this.data[field.id]" $required="field.required" $placeholder="field.placeholder || ''">
</template>
<template $if="field.type === 'textarea'">
<textarea class="form-control" $bind="this.data[field.id]" $required="field.required" $placeholder="field.placeholder || ''" $rows="field.rows || 3"></textarea>
</template>
<template $if="field.type === 'select'">
<select class="form-select" $bind="this.data[field.id]" $required="field.required">
<template $each="field.options" as="opt">
<option $value="typeof opt === 'object' ? opt.value : opt" $text="typeof opt === 'object' ? opt.label : opt"></option>
</template>
</select>
</template>
<template $if="field.type === 'switch'">
<div class="form-check form-switch mt-1">
<input class="form-check-input" type="checkbox" $bind="this.data[field.id]">
</div>
</template>
<template $each="AutoForm.customTypes" as="type">
<template $if="field.type === type">
<div $is="type" $.data="this.data[field.id]" $onchange="this.data[field.id] = event.detail"></div>
</template>
</template>
</div>
</div>
</template>
</div>
<div $if="!this.state?.hideSubmit" class="mt-4 pt-2 border-top d-flex justify-content-end gap-2">
<button type="button" class="btn btn-light px-4" $if="this.state?.showCancel" $onclick="this.dispatchEvent(new CustomEvent('cancel'))">{#Cancel#}</button>
<button type="submit" class="btn btn-primary px-4" $disabled="this.state?.formState === 'submitting'">
<span $if="this.state?.formState === 'submitting'" class="spinner-border spinner-border-sm me-2"></span>
<span $text="this.state?.submitLabel || '{#Submit#}'"></span>
</button>
</div>
</form>
`
));
Component.register("TagsInput", (container) => {
container._thisObj = container;
if (!container.state) container.state = NewState({ tags: [] });
container.addEventListener("bind", (e) => {
container.state.tags = Array.isArray(e.detail) ? e.detail : [];
});
}, Util.makeDom(
/*html*/
`
<div class="form-control d-flex flex-wrap gap-1 align-items-center" style="min-height:38px;cursor:text">
<template $each="this.state.tags">
<button type="button" class="btn btn-sm btn-outline-primary rounded-pill py-0 px-2" $onkeydown="${Util.getFunctionBody(function(event) {
if (["Backspace", "Delete"].includes(event.key)) {
event.preventDefault();
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] : $(this, "input")).focus();
});
}
})}" $text="item"></button>
</template>
<input type="text" class="border-0 shadow-none py-0 px-2 flex-grow-1 bg-transparent" placeholder="{#new tag name#}" style="min-width:100px;width:0;outline:none" $onkeydown="${Util.getFunctionBody(function(event) {
if (event.isComposing) return;
if (["Enter", ",", " "].includes(event.key)) {
event.preventDefault();
const v = thisNode.value.trim();
if (v && !this.state.tags.includes(v)) {
this.state.tags.push(v);
this.state.tags = this.state.tags;
this.dispatchEvent(new CustomEvent("change", { bubbles: false, detail: this.state.tags }));
}
thisNode.value = "";
}
})}">
</div>
`
), Util.makeDom(
/*html*/
`<style>TagsInput button:focus {background-color:var(--bs-btn-hover-bg);color:var(--bs-btn-hover-color)}</style>`
));
AutoForm.register("TagsInput");
global.AutoForm = AutoForm;
})(globalThis);
(function(global) {
const { Component, Util } = global;
const VirtualScroll = (options = {}) => {
const config = {
itemHeight: 50,
buffer: 5,
...options
};
return (container) => {
if (!container.state.list) container.state.list = [];
if (!container.state._renderedList) container.state._renderedList = [];
let _ticking = false;
const update = () => {
const list = container.state.list || [];
const scrollTop = container.scrollTop;
const containerHeight = container.clientHeight;
const itemHeight = config.itemHeight;
const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - config.buffer);
const endIndex = Math.min(list.length, Math.ceil((scrollTop + containerHeight) / itemHeight) + config.buffer);
const rendered = [];
for (let i = startIndex; i < endIndex; i++) {
rendered.push({
...list[i],
_top: i * itemHeight,
_index: i
});
}
container.state._renderedList = rendered;
const spacerPost = container.querySelector(".dt-spacer-post");
if (spacerPost) {
spacerPost.style.height = list.length * itemHeight - endIndex * itemHeight + "px";
spacerPost.style.display = "block";
}
const spacerPrev = container.querySelector(".dt-spacer-prev");
if (spacerPrev) {
spacerPrev.style.height = startIndex * itemHeight + "px";
spacerPrev.style.display = "block";
}
};
container.addEventListener("scroll", () => {
if (!_ticking) {
window.requestAnimationFrame(() => {
update();
_ticking = false;
});
_ticking = true;
}
});
container.state.__watch("list", update);
window.addEventListener("resize", update);
Promise.resolve().then(update);
};
};
Component.register("FastList", (container) => {
const itemHeights = /* @__PURE__ */ new Map();
container.state.renderedList = [];
Util.newAvg();
const update = () => {
const list = container.state.list || [];
container.state.groups || [];
const scrollTop = container.scrollTop;
const viewHeight = container.clientHeight;
const itemHeight = container.state.itemHeight || 40;
let currentTop = 0;
let startIndex = -1;
let endIndex = list.length;
for (let i = 0; i < list.length; i++) {
const h = itemHeights.get(list[i].id) || itemHeight;
if (startIndex === -1 && currentTop + h > scrollTop - 200) startIndex = i;
if (startIndex !== -1 && currentTop > scrollTop + viewHeight + 200) {
endIndex = i;
break;
}
currentTop += h;
}
if (startIndex === -1) startIndex = 0;
container.state.renderedList = list.slice(startIndex, endIndex).map((item, i) => ({ ...item, _index: startIndex + i }));
const prevH = list.slice(0, startIndex).reduce((s, item) => s + (itemHeights.get(item.id) || itemHeight), 0);
const postH = list.slice(endIndex).reduce((s, item) => s + (itemHeights.get(item.id) || itemHeight), 0);
const prev = container.querySelector(".list-spacer-prev");
const post = container.querySelector(".list-spacer-post");
if (prev) prev.style.height = prevH + "px";
if (post) post.style.height = postH + "px";
};
container.addEventListener("scroll", update);
container.state.__watch("list", update);
Promise.resolve().then(update);
}, Util.makeDom(
/*html*/
`
<div class="fast-list-root overflow-auto h-100">
<div class="list-spacer-prev"></div>
<div class="list-container">
<template $each="this.state.renderedList" key="id">
<div slot-id="item"></div>
</template>
</div>
<div class="list-spacer-post"></div>
</div>
`
));
global.VirtualScroll = VirtualScroll;
})(globalThis);
(function(global) {
const { Component, Util } = global;
const MouseMover = {
bind: (handle, target, options = {}) => {
let isMoving = false;
let startX, startY, startLeft, startTop;
const onMouseDown = (e) => {
if (options.shouldStart && !options.shouldStart(e)) return;
isMoving = true;
startX = e.clientX;
startY = e.clientY;
const rect = target.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
if (options.onStart) options.onStart(e);
};
const onMouseMove = (e) => {
if (!isMoving) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
if (options.axis !== "y") target.style.left = startLeft + dx + "px";
if (options.axis !== "x") target.style.top = startTop + dy + "px";
if (options.onMove) options.onStart(e);
};
const onMouseUp = (e) => {
isMoving = false;
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
if (options.onEnd) options.onEnd(e);
};
handle.addEventListener("mousedown", onMouseDown);
return () => handle.removeEventListener("mousedown", onMouseDown);
}
};
Component.register("Resizer", (container) => {
container.style.cursor = container.hasAttribute("vertical") ? "col-resize" : "row-resize";
container.addEventListener("mousedown", (e) => {
const target = container.parentElement;
if (!target) return;
const rect = target.getBoundingClientRect();
const startX = e.clientX;
const startY = e.clientY;
const startW = rect.width;
const startH = rect.height;
const onMouseMove = (e2) => {
const dw = e2.clientX - startX;
const dh = e2.clientY - startY;
if (container.hasAttribute("vertical")) target.style.width = startW + dw + "px";
else target.style.height = startH + dh + "px";
container.dispatchEvent(new CustomEvent("resize", { bubbles: false, detail: { width: target.offsetWidth, height: target.offsetHeight } }));
};
const onMouseUp = () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
});
}, Util.makeDom(
/*html*/
`<div style="position:absolute;right:0;bottom:0;width:10px;height:10px;z-index:100"></div>`
));
global.MouseMover = MouseMover;
})(globalThis);
(function(global) {
const { Component, Util } = global;
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", "
Component.register("Icon", (container) => {
container.state.name = container.getAttribute("name");
}, Util.makeDom(`<i $class="bi bi-\${this.state.name}"></i>`));
Component.register("IconSelector", (container) => {
container.state.icons = BOOTSTRAP_ICONS;
container.state.search = "";
container.state.selected = "";
container.state.__watch("search", (v) => {
const s = v.toLowerCase();
container.state.icons = BOOTSTRAP_ICONS.filter((i) => i.includes(s));
});
container.select = (icon) => {
container.state.selected = icon;
container.dispatchEvent(new CustomEvent("change", { bubbles: false, detail: icon }));
};
}, Util.makeDom(
/*html*/
`
<div class="icon-selector-root">
<input type="text" class="form-control mb-2" $bind="this.state.search" placeholder="Search icons...">
<div class="icon-grid d-flex flex-wrap gap-2 overflow-auto" style="max-height: 200px;">
<template $each="this.state.icons" as="icon">
<div $class="icon-item p-2 border rounded \${this.state.selected === icon ? 'bg-primary text-white' : ''}"
style="cursor:pointer; width:40px; height:40px"
$onclick="this.select(icon)">
<i $class="bi bi-\${icon}"></i>
</div>
</template>
</div>
</div>
`
));
global.BOOTSTRAP_ICONS = BOOTSTRAP_ICONS;
})(globalThis);
(function(global) {
const { Component, Hash, Util } = global;
Component.register("Nav", (container) => {
container.state.items = [];
container.state.activeId = Hash.nav;
container.state.__watch("activeId", (v) => Hash.nav = v);
window.addEventListener("hashchange", () => container.state.activeId = Hash.nav);
container.select = (id) => {
container.state.activeId = id;
container.dispatchEvent(new CustomEvent("change", { bubbles: false, detail: id }));
};
}, Util.makeDom(
/*html*/
`
<ul class="nav nav-pills flex-column">
<template $each="this.state.items" as="item">
<li class="nav-item">
<a $class="nav-link \${this.state.activeId === item.id ? 'active' : ''}"
href="#" $onclick="this.select(item.id)">
<i $if="item.icon" $class="bi bi-\${item.icon} me-2"></i>
<span $text="item.label"></span>
</a>
</li>
</template>
</ul>
`
));
Component.register("Breadcrumb", (container) => {
container.state.items = [];
}, Util.makeDom(
/*html*/
`
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<template $each="this.state.items" as="item">
<li $class="breadcrumb-item \${index === (this.state.items || []).length - 1 ? 'active' : ''}">
<template $if="index < (this.state.items || []).length - 1">
<a href="#" $onclick="this.dispatchEvent(new CustomEvent('select', {detail: item}))" $text="item.label"></a>
</template>
<template $if="index === (this.state.items || []).length - 1">
<span $text="item.label"></span>
</template>
</li>
</template>
</ol>
</nav>
`
));
})(globalThis);
if (typeof document !== "undefined") {
window.addEventListener("beforeunload", (event) => {
var _a;
if (((_a = globalThis.State) == null ? void 0 : _a.exitBlocks) > 0) {
event.preventDefault();
event.returnValue = "";
}
});
}
})();