/* === storage/rsx-build/bundles/npm_Bootstrap5_Bundle_6459e8ed0f60bda4f121420766012d53.js === */
(() => {
// node_modules/@jqhtml/core/dist/index.js
var LifecycleManager = class _LifecycleManager {
static get_instance() {
if (!_LifecycleManager.instance) {
_LifecycleManager.instance = new _LifecycleManager();
}
return _LifecycleManager.instance;
}
constructor() {
this.active_components = /* @__PURE__ */ new Set();
}
/**
* Boot a component - run its full lifecycle
* Called when component is created
*/
async boot_component(component) {
this.active_components.add(component);
try {
await component.create();
if (component._stopped)
return;
component.trigger("create");
let render_id = component._render();
if (component._stopped)
return;
await component.load();
if (component._stopped)
return;
if (component.should_rerender()) {
render_id = component._render();
if (component._stopped)
return;
}
if (component._render_count !== render_id) {
return;
}
await component.ready();
if (component._stopped)
return;
await component.trigger("ready");
} catch (error) {
console.error(`Error booting component ${component.component_name()}:`, error);
throw error;
}
}
/**
* Unregister a component (called on destroy)
*/
unregister_component(component) {
this.active_components.delete(component);
}
/**
* Wait for all active components to reach ready state
*/
async wait_for_ready() {
const ready_promises = [];
for (const component of this.active_components) {
if (component._ready_state < 4) {
ready_promises.push(new Promise((resolve) => {
component.on("ready", () => resolve());
}));
}
}
await Promise.all(ready_promises);
}
};
var component_classes = /* @__PURE__ */ new Map();
var component_templates = /* @__PURE__ */ new Map();
var warned_components = /* @__PURE__ */ new Set();
var DEFAULT_TEMPLATE = {
name: "Component",
// Default name
tag: "div",
render: function(data, args, content) {
const _output = [];
if (args._inner_html) {
_output.push(args._inner_html);
return [_output, this];
}
if (content && typeof content === "function") {
const result = content(this);
if (Array.isArray(result) && result.length === 2) {
_output.push(...result[0]);
} else if (typeof result === "string") {
_output.push(result);
}
}
return [_output, this];
}
};
function register_component(nameOrClass, component_class, template) {
if (typeof nameOrClass === "string") {
const name = nameOrClass;
if (!component_class) {
throw new Error("Component class is required when registering by name");
}
if (!/^[A-Z]/.test(name)) {
throw new Error(`Component name '${name}' must start with a capital letter. Convention is First_Letter_With_Underscores.`);
}
component_classes.set(name, component_class);
if (template) {
if (template.name !== name) {
throw new Error(`Template name '${template.name}' must match component name '${name}'`);
}
register_template(template);
}
} else {
const component_class2 = nameOrClass;
const name = component_class2.name;
if (!name || name === "Component") {
throw new Error("Component class must have a name when registering without explicit name");
}
component_classes.set(name, component_class2);
}
}
function get_component_class(name) {
const directClass = component_classes.get(name);
if (directClass) {
return directClass;
}
const template = component_templates.get(name);
if (template && template.extends) {
const visited = /* @__PURE__ */ new Set([name]);
let currentTemplateName = template.extends;
while (currentTemplateName && !visited.has(currentTemplateName)) {
visited.add(currentTemplateName);
const parentClass = component_classes.get(currentTemplateName);
if (parentClass) {
if (window.jqhtml?.debug?.enabled) {
console.log(`[JQHTML] Component '${name}' using class from parent '${currentTemplateName}' via extends chain`);
}
return parentClass;
}
const parentTemplate = component_templates.get(currentTemplateName);
if (parentTemplate && parentTemplate.extends) {
currentTemplateName = parentTemplate.extends;
} else {
break;
}
}
}
return void 0;
}
function register_template(template_def) {
const name = template_def.name;
if (!name) {
throw new Error("Template must have a name property");
}
if (!/^[A-Z]/.test(name)) {
throw new Error(`Template name '${name}' must start with a capital letter. Convention is First_Letter_With_Underscores.`);
}
if (component_templates.has(name)) {
console.warn(`[JQHTML] Template '${name}' already registered, skipping duplicate registration`);
return false;
}
component_templates.set(name, template_def);
if (window.jqhtml?.debug?.enabled) {
console.log(`[JQHTML] Successfully registered template: ${name}`);
}
const component_class = component_classes.get(name);
if (component_class) {
component_class._jqhtml_metadata = {
tag: template_def.tag,
defaultAttributes: template_def.defaultAttributes || {}
};
}
return true;
}
function get_template(name) {
const template = component_templates.get(name);
if (!template) {
const component_class = component_classes.get(name);
if (component_class) {
const inherited_template = get_template_by_class(component_class);
if (inherited_template !== DEFAULT_TEMPLATE) {
if (window.jqhtml?.debug?.enabled) {
console.log(`[JQHTML] Component '${name}' has no template, using template from prototype chain`);
}
return inherited_template;
}
if (window.jqhtml?.debug?.enabled && !warned_components.has(name)) {
warned_components.add(name);
console.log(`[JQHTML] No template found for class: ${name}, using default div template`);
}
} else {
if (name !== "_Jqhtml_Component" && name !== "Redrawable" && !warned_components.has(name)) {
warned_components.add(name);
console.warn(`[JQHTML] Creating ${name} with defaults - no template or class defined`);
}
}
if (window.jqhtml?.debug?.verbose) {
const registered = Array.from(component_templates.keys());
console.log(`[JQHTML] Looking for template '${name}' in: [${registered.join(", ")}]`);
}
return DEFAULT_TEMPLATE;
}
return template;
}
function get_template_by_class(component_class) {
if (component_class.template) {
return component_class.template;
}
let currentClass = component_class;
while (currentClass && currentClass.name !== "Object") {
let normalizedName = currentClass.name;
if (normalizedName === "_Jqhtml_Component" || normalizedName === "_Base_Jqhtml_Component") {
normalizedName = "Component";
}
const template = component_templates.get(normalizedName);
if (template) {
return template;
}
currentClass = Object.getPrototypeOf(currentClass);
}
return DEFAULT_TEMPLATE;
}
function create_component(name, element, args = {}) {
const ComponentClass = get_component_class(name) || Component;
return new ComponentClass(element, args);
}
function has_component(name) {
return component_classes.has(name);
}
function get_component_names() {
return Array.from(component_classes.keys());
}
function get_registered_templates() {
return Array.from(component_templates.keys());
}
function list_components() {
const result = {};
for (const name of component_classes.keys()) {
result[name] = {
has_class: true,
has_template: component_templates.has(name)
};
}
for (const name of component_templates.keys()) {
if (!result[name]) {
result[name] = {
has_class: false,
has_template: true
};
}
}
return result;
}
var _cid_increment = "aa";
function uid() {
const current = _cid_increment;
const chars = _cid_increment.split("");
let carry = true;
for (let i = chars.length - 1; i >= 0 && carry; i--) {
const char = chars[i];
if (char >= "a" && char < "z") {
chars[i] = String.fromCharCode(char.charCodeAt(0) + 1);
carry = false;
} else if (char === "z") {
chars[i] = "0";
carry = false;
} else if (char >= "0" && char < "9") {
chars[i] = String.fromCharCode(char.charCodeAt(0) + 1);
carry = false;
} else if (char === "9") {
chars[i] = "a";
carry = true;
}
}
if (carry) {
chars.unshift("a");
}
if (chars[0] >= "0" && chars[0] <= "9") {
chars[0] = "a";
chars.unshift("a");
}
_cid_increment = chars.join("");
return current;
}
function process_instructions(instructions, target, context, slots) {
const html = [];
const tagElements = {};
const components = {};
for (const instruction of instructions) {
process_instruction_to_html(instruction, html, tagElements, components, context, slots);
}
target[0].innerHTML = html.join("");
for (const [tid, tagData] of Object.entries(tagElements)) {
const el = target[0].querySelector(`[data-tid="${tid}"]`);
if (el) {
const element = $(el);
el.removeAttribute("data-tid");
apply_attributes(element, tagData.attrs, context);
}
}
for (const [cid, compData] of Object.entries(components)) {
const el = target[0].querySelector(`[data-cid="${cid}"]`);
if (el) {
const element = $(el);
el.removeAttribute("data-cid");
initialize_component(element, compData);
}
}
}
function process_instruction_to_html(instruction, html, tagElements, components, context, slots) {
if (typeof instruction === "string") {
html.push(instruction);
} else if ("tag" in instruction) {
process_tag_to_html(instruction, html, tagElements, components, context);
} else if ("comp" in instruction) {
process_component_to_html(instruction, html, components, context);
} else if ("slot" in instruction) {
process_slot_to_html(instruction, html, tagElements, components, context, slots);
} else if ("rawtag" in instruction) {
process_rawtag_to_html(instruction, html);
}
}
function process_tag_to_html(instruction, html, tagElements, components, context) {
const [tagName, attrs, selfClosing] = instruction.tag;
const needsTracking = Object.keys(attrs).some((key) => key === "$id" || key.startsWith("$") || key.startsWith("@") || key.startsWith("on") || key.startsWith("data-bind-") || key.startsWith("data-on-"));
html.push(`<${tagName}`);
let tid = null;
if (needsTracking) {
tid = uid();
html.push(` data-tid="${tid}"`);
tagElements[tid] = { attrs, context };
}
for (const [key, value] of Object.entries(attrs)) {
if (!key.startsWith("$") && !key.startsWith("on") && !key.startsWith("@") && !key.startsWith("data-bind-") && !key.startsWith("data-on-") && (typeof value === "string" || typeof value === "number")) {
if (key === "id" && tid) {
html.push(` id="${value}:${context._cid}"`);
} else {
html.push(` ${key}="${value}"`);
}
}
}
if (selfClosing) {
html.push(" />");
} else {
html.push(">");
}
}
function process_component_to_html(instruction, html, components, context) {
const [componentName, props, contentFn] = instruction.comp;
const cid = uid();
get_component_class(componentName) || Component;
const template = get_template(componentName);
const tagName = props._tag || template.tag || "div";
html.push(`<${tagName} data-cid="${cid}"`);
if (props["data-id"]) {
const baseId = props["data-id"];
html.push(` id="${props["id"]}" data-id="${baseId}"`);
} else if (props["id"]) {
html.push(` id="${props["id"]}"`);
}
html.push(">" + tagName + ">");
components[cid] = {
name: componentName,
props,
contentFn,
context
};
}
function process_slot_to_html(instruction, html, tagElements, components, context, parentSlots) {
const [slotName] = instruction.slot;
if (parentSlots && slotName in parentSlots) {
const parentSlot = parentSlots[slotName];
const [, slotProps, contentFn] = parentSlot.slot;
const [content] = contentFn.call(context, slotProps);
for (const item of content) {
process_instruction_to_html(item, html, tagElements, components, context);
}
} else if (slotName === "default" && instruction.slot[2]) {
const [, , defaultFn] = instruction.slot;
const [content] = defaultFn.call(context, {});
for (const item of content) {
process_instruction_to_html(item, html, tagElements, components, context);
}
}
}
function process_rawtag_to_html(instruction, html) {
const [tagName, attrs, rawContent] = instruction.rawtag;
html.push(`<${tagName}`);
for (const [key, value] of Object.entries(attrs)) {
if (typeof value === "string" || typeof value === "number") {
const escaped_value = String(value).replace(/"/g, """);
html.push(` ${key}="${escaped_value}"`);
} else if (typeof value === "boolean" && value) {
html.push(` ${key}`);
}
}
html.push(">");
const escaped_content = rawContent.replace(/&/g, "&").replace(//g, ">");
html.push(escaped_content);
html.push(`${tagName}>`);
}
function apply_attributes(element, attrs, context) {
for (const [key, value] of Object.entries(attrs)) {
if (key === "$id" || key === "id") {
continue;
} else if (key.startsWith("$")) {
const dataKey = key.substring(1);
element.data(dataKey, value);
context.args[dataKey] = value;
if (typeof value == "string" || typeof value == "number") {
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(`data-${dataKey}`, attrValue);
}
} else if (key.startsWith("data-on-")) {
const eventName = key.substring(8);
if (typeof value === "function") {
element.on(eventName, function(e) {
value.bind(context)(e, element);
});
} else {
console.warn("(JQHTML) Tried to assign a non function to on event handler " + key);
}
} else if (key.startsWith("on")) {
const eventName = key.substring(2);
if (typeof value === "function") {
element.on(eventName, function(e) {
value.bind(context)(e, element);
});
} else {
console.warn("(JQHTML) Tried to assign a non function to on event handler " + key);
}
} else if (key.startsWith("data-")) {
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(key, attrValue);
const dataKey = key.substring(5);
element.data(dataKey, value);
context.args[dataKey] = value;
} else if (key === "class") {
const existingClasses = element.attr("class");
if (window.jqhtml?.debug?.enabled) {
console.log(`[InstructionProcessor] Merging class attribute:`, {
existing: existingClasses,
new: value
});
}
if (!existingClasses) {
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr("class", attrValue);
} else {
const existing = existingClasses.split(/\s+/).filter((c) => c);
const newClasses = String(value).split(/\s+/).filter((c) => c);
for (const newClass of newClasses) {
if (!existing.includes(newClass)) {
existing.push(newClass);
}
}
element.attr("class", existing.join(" "));
}
if (window.jqhtml?.debug?.enabled) {
console.log(`[InstructionProcessor] Class after merge:`, element.attr("class"));
}
} else if (key === "style") {
const existingStyle = element.attr("style");
if (!existingStyle) {
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr("style", attrValue);
} else {
const styleMap = {};
existingStyle.split(";").forEach((rule) => {
const [prop, val] = rule.split(":").map((s) => s.trim());
if (prop && val) {
styleMap[prop] = val;
}
});
String(value).split(";").forEach((rule) => {
const [prop, val] = rule.split(":").map((s) => s.trim());
if (prop && val) {
styleMap[prop] = val;
}
});
const mergedStyle = Object.entries(styleMap).map(([prop, val]) => `${prop}: ${val}`).join("; ");
element.attr("style", mergedStyle);
}
} else {
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
const attrValue = typeof value === "string" ? value.trim() : String(value);
element.attr(key, attrValue);
} else if (typeof value === "object") {
console.warn(`(JQHTML) Unexpected value for '${key}' on`, element);
}
}
}
}
async function initialize_component(element, compData) {
const { name, props, contentFn, context } = compData;
const ComponentClass = get_component_class(name) || Component;
const invocationAttrs = {};
for (const [key, value] of Object.entries(props)) {
if (!key.startsWith("_")) {
invocationAttrs[key] = value;
}
}
if (window.jqhtml?.debug?.enabled) {
console.log(`[InstructionProcessor] Applying invocation attributes for ${name}:`, invocationAttrs);
}
apply_attributes(element, invocationAttrs, context);
const options = {};
if (contentFn) {
options._innerhtml_function = contentFn;
}
if (ComponentClass.name !== name) {
options._component_name = name;
}
const instance = new ComponentClass(element, options);
instance._instantiator = context;
await instance.boot();
}
function extract_slots(instructions) {
const slots = {};
for (const instruction of instructions) {
if (typeof instruction === "object" && "slot" in instruction) {
const [name] = instruction.slot;
slots[name] = instruction;
}
}
return slots;
}
var performanceMetrics = /* @__PURE__ */ new Map();
function devWarn(message) {
if (typeof window !== "undefined" && window.JQHTML_SUPPRESS_WARNINGS) {
return;
}
if (typeof process !== "undefined" && process.env && false) {
return;
}
console.warn(`[JQHTML Dev Warning] ${message}`);
}
function getJqhtml$1() {
if (typeof window !== "undefined" && window.jqhtml) {
return window.jqhtml;
}
if (typeof globalThis !== "undefined" && globalThis.jqhtml) {
return globalThis.jqhtml;
}
throw new Error("FATAL: window.jqhtml is not defined. The JQHTML runtime must be loaded before using debug features. Import and initialize @jqhtml/core before attempting to use debug functionality.");
}
function flashComponent(component, eventType) {
const jqhtml2 = getJqhtml$1();
if (!jqhtml2?.debug?.flashComponents)
return;
const duration = jqhtml2.debug.flashDuration || 500;
const colors = jqhtml2.debug.flashColors || {};
const color = colors[eventType] || (eventType === "create" ? "#3498db" : eventType === "render" ? "#27ae60" : "#9b59b6");
const originalBorder = component.$.css("border");
component.$.css({
"border": `2px solid ${color}`,
"transition": `border ${duration}ms ease-out`
});
setTimeout(() => {
component.$.css("border", originalBorder || "");
}, duration);
}
function logLifecycle(component, phase, status) {
const jqhtml2 = getJqhtml$1();
if (!jqhtml2?.debug)
return;
const shouldLog = jqhtml2.debug.logFullLifecycle || jqhtml2.debug.logCreationReady && (phase === "create" || phase === "ready");
if (!shouldLog)
return;
const componentName = component.constructor.name;
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
const prefix = `[JQHTML ${timestamp}]`;
if (status === "start") {
console.log(`${prefix} ${componentName}#${component._cid} \u2192 ${phase} starting...`);
if (jqhtml2.debug.profilePerformance) {
performanceMetrics.set(`${component._cid}_${phase}`, Date.now());
}
} else {
let message = `${prefix} ${componentName}#${component._cid} \u2713 ${phase} complete`;
if (jqhtml2.debug.profilePerformance) {
const startTime = performanceMetrics.get(`${component._cid}_${phase}`);
if (startTime) {
const duration = Date.now() - startTime;
message += ` (${duration}ms)`;
if (phase === "render" && jqhtml2.debug.highlightSlowRenders && duration > jqhtml2.debug.highlightSlowRenders) {
console.warn(`${prefix} SLOW RENDER: ${componentName}#${component._cid} took ${duration}ms`);
component.$.css("outline", "2px dashed red");
}
}
}
console.log(message);
if (jqhtml2.debug.flashComponents && (phase === "create" || phase === "render" || phase === "ready")) {
flashComponent(component, phase);
}
}
if (jqhtml2.debug.showComponentTree) {
updateComponentTree();
}
}
function applyDebugDelay(phase) {
const jqhtml2 = getJqhtml$1();
if (!jqhtml2?.debug)
return;
let delayMs = 0;
switch (phase) {
case "component":
delayMs = jqhtml2.debug.delayAfterComponent || 0;
break;
case "render":
delayMs = jqhtml2.debug.delayAfterRender || 0;
break;
case "rerender":
delayMs = jqhtml2.debug.delayAfterRerender || 0;
break;
}
if (delayMs > 0) {
console.log(`[JQHTML Debug] Applying ${delayMs}ms delay after ${phase}`);
}
}
function updateComponentTree() {
console.log("[JQHTML Tree] Component hierarchy updated");
}
var Component = class _Jqhtml_Component {
constructor(element, args = {}) {
this.data = {};
this._ready_state = 0;
this._instantiator = null;
this._dom_parent = null;
this._dom_children = /* @__PURE__ */ new Set();
this._use_dom_fallback = false;
this._stopped = false;
this._booted = false;
this._data_before_render = null;
this._lifecycle_callbacks = /* @__PURE__ */ new Map();
this._lifecycle_states = /* @__PURE__ */ new Set();
this.__loading = false;
this._did_first_render = false;
this._render_count = 0;
this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance();
if (element) {
this.$ = $(element);
} else {
const div = document.createElement("div");
this.$ = $(div);
}
const dataAttrs = {};
if (this.$.length > 0) {
const dataset = this.$[0].dataset || {};
for (const key in dataset) {
if (key !== "cid" && key !== "tid" && key !== "componentName" && key !== "readyState") {
const dataValue = this.$.data(key);
if (dataValue !== void 0 && dataValue !== dataset[key]) {
dataAttrs[key] = dataValue;
} else {
dataAttrs[key] = dataset[key];
}
}
}
}
let template_for_args;
if (args._component_name) {
template_for_args = get_template(args._component_name);
} else {
template_for_args = get_template_by_class(this.constructor);
}
const defineArgs = template_for_args?.defineArgs || {};
this.args = { ...defineArgs, ...dataAttrs, ...args };
for (const [key, value] of Object.entries(this.args)) {
if (key === "cid" || key === "tid" || key === "componentName" || key === "readyState" || key.startsWith("_")) {
continue;
}
if (typeof value === "string" || typeof value === "number") {
try {
const currentAttr = this.$.attr(`data-${key}`);
if (currentAttr != value) {
this.$.attr(`data-${key}`, String(value));
}
} catch (e) {
}
}
}
this.$.data("_component", this);
this._apply_css_classes();
this._apply_default_attributes();
this._set_attributes();
this._find_dom_parent();
this._log_lifecycle("construct", "complete");
}
/**
* Boot - Start the full component lifecycle
* Called immediately after construction by instruction processor
*/
async boot() {
if (this._booted)
return;
this._booted = true;
await this._lifecycle_manager.boot_component(this);
}
// -------------------------------------------------------------------------
// Lifecycle Methods (called by LifecycleManager)
// -------------------------------------------------------------------------
/**
* Internal render phase - Create DOM structure
* Called top-down (parent before children) when part of lifecycle
* This is an internal method - users should call render() instead
*
* @param id Optional scoped ID - if provided, delegates to child component's _render()
* @returns The current _render_count after incrementing (used to detect stale renders)
* @private
*/
_render(id = null) {
this._render_count++;
const current_render_id = this._render_count;
if (this._stopped)
return current_render_id;
if (id) {
const $element = this.$sid(id);
if ($element.length === 0) {
throw new Error(`[JQHTML] render("${id}") - no such id.
Component "${this.component_name()}" has no child element with $id="${id}".`);
}
const child = $element.data("_component");
if (!child) {
throw new Error(`[JQHTML] render("${id}") - element is not a component or does not have $redrawable attribute set.
Element with $id="${id}" exists but is not initialized as a component.
Add $redrawable attribute or make it a proper component.`);
}
return child._render();
}
if (this.__loading) {
throw new Error(`[JQHTML] Component "${this.component_name()}" attempted to call render() during on_load().
on_load() should ONLY modify this.data. DOM updates happen automatically after on_load() completes.
Fix: Remove the this.render() call from on_load().
The framework will automatically re-render if this.data changes during on_load().`);
}
this._log_lifecycle("render", "start");
if (!$.contains(document.documentElement, this.$[0])) {
this._use_dom_fallback = true;
} else {
this._use_dom_fallback = false;
}
if (this._did_first_render) {
this.$.find(".Component").each(function() {
const child = $(this).data("_component");
if (child && !child._stopped) {
child._stop();
}
});
this.$[0].innerHTML = "";
} else {
this._did_first_render = true;
}
this.$.removeClass("_Component_Stopped");
if (this._data_before_render === null) {
this._data_before_render = JSON.stringify(this.data);
}
this._dom_children.clear();
let template_def;
if (this.args._component_name) {
template_def = get_template(this.args._component_name);
} else {
template_def = get_template_by_class(this.constructor);
}
if (template_def && template_def.render) {
const jqhtml2 = {
escape_html: (str) => {
const div = document.createElement("div");
div.textContent = String(str);
return div.innerHTML;
}
};
const defaultContent = () => "";
let [instructions, context] = template_def.render.bind(this)(
this.data,
this.args,
this.args._innerhtml_function || defaultContent,
// Content function with fallback
jqhtml2
// Utilities object
);
if (instructions && typeof instructions === "object" && instructions._slots && !Array.isArray(instructions)) {
const componentName = template_def.name || this.args._component_name || this.constructor.name;
console.log(`[JQHTML] Slot-only template detected for ${componentName}`);
let parentTemplate = null;
let parentTemplateName = null;
if (template_def.extends) {
console.log(`[JQHTML] Using explicit extends: ${template_def.extends}`);
parentTemplate = get_template(template_def.extends);
parentTemplateName = template_def.extends;
}
if (!parentTemplate) {
let currentClass = Object.getPrototypeOf(this.constructor);
while (currentClass && currentClass.name !== "Object" && currentClass.name !== "Component") {
const className = currentClass.name;
console.log(`[JQHTML] Checking parent: ${className}`);
try {
const classTemplate = get_template(className);
if (classTemplate && classTemplate.name !== "Component") {
console.log(`[JQHTML] Found parent template: ${className}`);
parentTemplate = classTemplate;
parentTemplateName = className;
break;
}
} catch (error) {
console.warn(`[JQHTML] Error finding parent template ${className}:`, error);
}
currentClass = Object.getPrototypeOf(currentClass);
}
}
if (parentTemplate) {
try {
const childSlots = instructions._slots;
const contentFunction = (slotName, data) => {
if (childSlots[slotName] && typeof childSlots[slotName] === "function") {
const [slotInstructions, slotContext] = childSlots[slotName](data);
return [slotInstructions, slotContext];
}
return "";
};
const [parentInstructions, parentContext] = parentTemplate.render.bind(this)(
this.data,
this.args,
contentFunction,
// Pass content function that invokes child slots
jqhtml2
);
console.log(`[JQHTML] Parent template invoked successfully`);
instructions = parentInstructions;
context = parentContext;
} catch (error) {
console.warn(`[JQHTML] Error invoking parent template ${parentTemplateName}:`, error);
instructions = [];
}
} else {
console.warn(`[JQHTML] No parent template found for ${this.constructor.name}, rendering empty`);
instructions = [];
}
}
const flattenedInstructions = this._flatten_instructions(instructions);
process_instructions(flattenedInstructions, this.$, this);
}
this._update_debug_attrs();
this._log_lifecycle("render", "complete");
const renderResult = this.on_render();
if (renderResult && typeof renderResult.then === "function") {
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). on_render() must be synchronous code. Remove 'async' from the function declaration.`);
}
this.trigger("render");
const isRerender = this._ready_state >= 3;
applyDebugDelay(isRerender ? "rerender" : "render");
return current_render_id;
}
/**
* Public render method - re-renders component and completes lifecycle
* This is what users should call when they want to update a component.
*
* Lifecycle sequence:
* 1. _render() - Updates DOM synchronously, calls on_render(), fires 'render' event
* 2. Async continuation (fire and forget):
* - _wait_for_children_ready() - Waits for all children to reach ready state
* - on_ready() - Calls user's ready hook
* - trigger('ready') - Fires ready event
*
* Returns immediately after _render() completes - does NOT wait for children
*/
render(id = null) {
if (this._stopped)
return;
if (id) {
const $element = this.$sid(id);
if ($element.length === 0) {
throw new Error(`[JQHTML] render("${id}") - no such id.
Component "${this.component_name()}" has no child element with $id="${id}".`);
}
const child = $element.data("_component");
if (!child) {
throw new Error(`[JQHTML] render("${id}") - element is not a component or does not have $redrawable attribute set.
Element with $id="${id}" exists but is not initialized as a component.
Add $redrawable attribute or make it a proper component.`);
}
return child.render();
}
const render_id = this._render();
(async () => {
await this._wait_for_children_ready();
if (this._render_count !== render_id) {
return;
}
await this.on_ready();
await this.trigger("ready");
})();
}
/**
* Alias for render() - re-renders component with current data
* Provided for API consistency and clarity
*/
redraw(id = null) {
return this.render(id);
}
/**
* Create phase - Quick setup, prepare UI
* Called bottom-up (children before parent)
*/
async create() {
if (this._stopped || this._ready_state >= 1)
return;
this._log_lifecycle("create", "start");
const result = this.on_create();
if (result && typeof result.then === "function") {
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_create(). on_create() must be synchronous code. Remove 'async' from the function declaration.`);
await result;
}
this._ready_state = 1;
this._update_debug_attrs();
this._log_lifecycle("create", "complete");
this.trigger("create");
}
/**
* Load phase - Fetch data from APIs
* Called bottom-up, fully parallel
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*/
async load() {
if (this._stopped || this._ready_state >= 2)
return;
this._log_lifecycle("load", "start");
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
this.__loading = true;
try {
await this.on_load();
} finally {
this.__loading = false;
}
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().
on_load() should ONLY modify this.data. The this.args property is read-only.
Before: ${argsBeforeLoad}
After: ${argsAfterLoad}
Fix: Move your modifications to this.data instead.`);
}
const newProperties = propertiesAfterLoad.filter((prop) => !propertiesBeforeLoad.has(prop) && prop !== "data");
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().
on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(", ")}
Fix: Store your data in this.data instead:
\u274C this.${newProperties[0]} = value;
\u2705 this.data.${newProperties[0]} = value;`);
}
this._ready_state = 2;
this._update_debug_attrs();
this._log_lifecycle("load", "complete");
this.trigger("load");
}
/**
* Ready phase - Component fully initialized
* Called bottom-up (children before parent)
*/
async ready() {
if (this._stopped || this._ready_state >= 4)
return;
this._log_lifecycle("ready", "start");
await this._wait_for_children_ready();
await this.on_ready();
this._ready_state = 4;
this._update_debug_attrs();
this._log_lifecycle("ready", "complete");
this.trigger("ready");
}
/**
* Wait for all child components to reach ready state
* Ensures bottom-up ordering (children ready before parent)
* @private
*/
async _wait_for_children_ready() {
const children = this._get_dom_children();
if (children.length === 0) {
return;
}
const ready_promises = [];
for (const child of children) {
if (child._ready_state >= 4) {
continue;
}
const ready_promise = new Promise((resolve) => {
child.on("ready", () => resolve());
});
ready_promises.push(ready_promise);
}
await Promise.all(ready_promises);
}
/**
* Reinitialize the component - full reset and re-initialization
* Wipes the innerHTML, resets data to empty, and runs full lifecycle
*/
async reinitialize() {
if (this._stopped)
return;
this._log_lifecycle("reinitialize", "start");
this.$[0].innerHTML = "";
this.data = {};
this._ready_state = 0;
this._data_before_render = null;
this._dom_children.clear();
await this._render();
await this.create();
await this.load();
if (this.should_rerender()) {
await this._render();
}
await this.ready();
this._log_lifecycle("reinitialize", "complete");
}
/**
* Reload component - re-fetch data and re-render
* Re-runs on_load(), always renders, and calls on_ready()
*/
async reload() {
if (this._stopped)
return;
this._log_lifecycle("reload", "start");
const has_custom_on_load = this.on_load !== _Jqhtml_Component.prototype.on_load;
if (has_custom_on_load) {
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
this.__loading = true;
try {
await this.on_load();
} finally {
this.__loading = false;
}
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().
on_load() should ONLY modify this.data. The this.args property is read-only.
Before: ${argsBeforeLoad}
After: ${argsAfterLoad}
Fix: Move your modifications to this.data instead.`);
}
const newProperties = propertiesAfterLoad.filter((prop) => !propertiesBeforeLoad.has(prop) && prop !== "data");
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().
on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(", ")}
Fix: Store your data in this.data instead:
\u274C this.${newProperties[0]} = value;
\u2705 this.data.${newProperties[0]} = value;`);
}
}
await this.render();
this._log_lifecycle("reload", "complete");
}
/**
* Destroy the component and cleanup
* Called automatically by MutationObserver when component is removed from DOM
* Can also be called manually for explicit cleanup
*/
/**
* Internal stop method - stops just this component (no children)
* Sets stopped flag, calls lifecycle hooks, but leaves DOM intact
* @private
*/
_stop() {
if (this._stopped)
return;
this._stopped = true;
const has_custom_destroy = this.on_destroy !== _Jqhtml_Component.prototype.on_destroy;
const has_destroy_callbacks = this._on_registered("destroy");
if (!has_custom_destroy && !has_destroy_callbacks) {
this._lifecycle_manager.unregister_component(this);
this._ready_state = 99;
return;
}
this._log_lifecycle("destroy", "start");
this.$.addClass("_Component_Stopped");
this._lifecycle_manager.unregister_component(this);
const destroyResult = this.on_destroy();
if (destroyResult && typeof destroyResult.then === "function") {
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_destroy(). on_destroy() must be synchronous code. Remove 'async' from the function declaration.`);
}
this.trigger("destroy");
this.$.trigger("destroy");
if (this._dom_parent) {
this._dom_parent._dom_children.delete(this);
}
this._ready_state = 99;
this._update_debug_attrs();
this._log_lifecycle("destroy", "complete");
}
/**
* Stop component lifecycle - stops all descendant components then self
* Leaves DOM intact, just stops lifecycle engine and fires cleanup hooks
*/
stop() {
this.$.find(".Component").each(function() {
const child = $(this).data("_component");
if (child && !child._stopped) {
child._stop();
}
});
this._stop();
}
// -------------------------------------------------------------------------
// Overridable Lifecycle Hooks
// -------------------------------------------------------------------------
on_render() {
}
on_create() {
}
async on_load() {
}
async on_ready() {
}
on_destroy() {
}
/**
* Should component re-render after load?
* By default, only re-renders if data has changed
* Override to control re-rendering behavior
*/
should_rerender() {
const currentDataState = JSON.stringify(this.data);
const dataChanged = this._data_before_render !== currentDataState;
if (dataChanged) {
this._data_before_render = currentDataState;
}
return dataChanged;
}
// -------------------------------------------------------------------------
// Public API
// -------------------------------------------------------------------------
/**
* Get component name for debugging
*/
component_name() {
return this.constructor.name;
}
/**
* Emit a jQuery event from component root
*/
emit(event_name, data) {
this._log_debug("emit", event_name, data);
this.$.trigger(event_name, data);
}
/**
* Register lifecycle event callback
* Allowed events: 'render', 'create', 'load', 'ready', 'destroy'
* Callbacks fire after the lifecycle method completes
* If the event has already occurred, the callback fires immediately AND registers for future occurrences
*/
on(event_name, callback) {
const allowed_events = ["render", "create", "load", "ready", "destroy"];
if (!allowed_events.includes(event_name)) {
console.error(`[JQHTML] Component.on() only supports lifecycle events: ${allowed_events.join(", ")}. Received: ${event_name}`);
return this;
}
if (!this._lifecycle_callbacks.has(event_name)) {
this._lifecycle_callbacks.set(event_name, []);
}
this._lifecycle_callbacks.get(event_name).push(callback);
if (this._lifecycle_states.has(event_name)) {
try {
callback(this);
} catch (error) {
console.error(`[JQHTML] Error in ${event_name} callback:`, error);
}
}
return this;
}
/**
* Trigger a lifecycle event - fires all registered callbacks
* Marks event as occurred so future .on() calls fire immediately
*/
trigger(event_name) {
this._lifecycle_states.add(event_name);
const callbacks = this._lifecycle_callbacks.get(event_name);
if (callbacks) {
for (const callback of callbacks) {
try {
callback.bind(this)(this);
} catch (error) {
console.error(`[JQHTML] Error in ${event_name} callback:`, error);
}
}
}
}
/**
* Check if any callbacks are registered for a given event
* Used to determine if cleanup logic needs to run
*/
_on_registered(event_name) {
const callbacks = this._lifecycle_callbacks.get(event_name);
return !!(callbacks && callbacks.length > 0);
}
/**
* Find element by scoped ID
*
* Searches for elements with id="local_id:THIS_COMPONENT_CID"
*
* Example:
* Template:
* Rendered:
* Access: this.$sid('save_btn') // Returns jQuery element
*
* Performance: Uses native document.getElementById() when component is in DOM,
* falls back to jQuery.find() for components not yet attached to DOM.
*
* @param local_id The local ID (without _cid suffix)
* @returns jQuery element with id="local_id:_cid", or empty jQuery object if not found
*/
$id(local_id) {
const scopedId = `${local_id}:${this._cid}`;
const el = document.getElementById(scopedId);
if (el) {
return $(el);
}
return this.$.find(`#${$.escapeSelector(scopedId)}`);
}
/**
* Get component instance by scoped ID
*
* Convenience method that finds element by scoped ID and returns the component instance.
*
* Example:
* Template:
* Access: const user = this.id('active_user'); // Returns User_Card instance
* user.data.name // Access component's data
*
* @param local_id The local ID (without _cid suffix)
* @returns Component instance or null if not found or not a component
*/
id(local_id) {
const element = this.$sid(local_id);
const component = element.data("_component");
if (!component && element.length > 0) {
console.warn(`Component ${this.constructor.name} tried to call .id('${local_id}') - ${local_id} exists, however, it is not a component or $redrawable. Did you forget to add $redrawable to the tag?`);
}
return component || null;
}
/**
* Get the component that instantiated this component (rendered it in their template)
* Returns null if component was created programmatically via $().component()
*/
instantiator() {
return this._instantiator;
}
/**
* Find descendant components by CSS selector
*/
find(selector) {
const components = [];
this.$.find(selector).each((_, el) => {
const comp = $(el).data("_component");
if (comp instanceof _Jqhtml_Component) {
components.push(comp);
}
});
return components;
}
/**
* Find closest ancestor component matching selector
*/
closest(selector) {
let current = this.$.parent();
while (current.length > 0) {
if (current.is(selector)) {
const comp = current.data("_component");
if (comp instanceof _Jqhtml_Component) {
return comp;
}
}
current = current.parent();
}
return null;
}
// -------------------------------------------------------------------------
// Static Methods
// -------------------------------------------------------------------------
/**
* Get CSS class hierarchy for this component type
*/
static get_class_hierarchy() {
const classes = [];
let ctor = this;
while (ctor) {
if (!ctor.name || typeof ctor.name !== "string") {
break;
}
if (ctor.name !== "Object" && ctor.name !== "") {
let normalizedName = ctor.name;
if (normalizedName === "_Jqhtml_Component" || normalizedName === "_Base_Jqhtml_Component") {
normalizedName = "Component";
}
classes.push(normalizedName);
}
const nextProto = Object.getPrototypeOf(ctor);
if (!nextProto || nextProto === Object.prototype || nextProto.constructor === Object) {
break;
}
ctor = nextProto;
}
return classes;
}
// -------------------------------------------------------------------------
// Private Implementation
// -------------------------------------------------------------------------
_generate_cid() {
return uid();
}
/**
* Flatten instruction array - converts ['_content', [...]] markers to flat array
* Recursively flattens nested content from content() calls
*/
_flatten_instructions(instructions) {
const result = [];
for (const instruction of instructions) {
if (Array.isArray(instruction) && instruction[0] === "_content" && Array.isArray(instruction[1])) {
const contentInstructions = this._flatten_instructions(instruction[1]);
result.push(...contentInstructions);
} else {
result.push(instruction);
}
}
return result;
}
_apply_css_classes() {
const hierarchy = this.constructor.get_class_hierarchy();
const classesToAdd = [...hierarchy];
if (this.args._component_name && this.args._component_name !== this.constructor.name) {
classesToAdd.unshift(this.args._component_name);
}
const publicClasses = classesToAdd.filter((className) => {
if (!className || typeof className !== "string") {
console.warn("[JQHTML] Filtered out invalid class name:", className);
return false;
}
return !className.startsWith("_");
});
if (publicClasses.length > 0) {
this.$.addClass(publicClasses.join(" "));
}
}
_apply_default_attributes() {
let template;
if (this.args._component_name) {
template = get_template(this.args._component_name);
} else {
template = get_template_by_class(this.constructor);
}
if (template && template.defaultAttributes) {
const defineAttrs = { ...template.defaultAttributes };
delete defineAttrs.tag;
if (window.jqhtml?.debug?.enabled) {
const componentName = template.name || this.args._component_name || this.constructor.name;
console.log(`[Component] Applying defaultAttributes for ${componentName}:`, defineAttrs);
}
for (const [key, value] of Object.entries(defineAttrs)) {
if (key === "class") {
const existingClasses = this.$.attr("class");
if (existingClasses) {
const existing = existingClasses.split(/\s+/).filter((c) => c);
const newClasses = String(value).split(/\s+/).filter((c) => c);
for (const newClass of newClasses) {
if (!existing.includes(newClass)) {
existing.push(newClass);
}
}
this.$.attr("class", existing.join(" "));
} else {
this.$.attr("class", value);
}
} else if (key === "style") {
const existingStyle = this.$.attr("style");
if (existingStyle) {
const existingRules = /* @__PURE__ */ new Map();
existingStyle.split(";").forEach((rule) => {
const [prop, val] = rule.split(":").map((s) => s.trim());
if (prop && val)
existingRules.set(prop, val);
});
String(value).split(";").forEach((rule) => {
const [prop, val] = rule.split(":").map((s) => s.trim());
if (prop && val)
existingRules.set(prop, val);
});
const merged = Array.from(existingRules.entries()).map(([prop, val]) => `${prop}: ${val}`).join("; ");
this.$.attr("style", merged);
} else {
this.$.attr("style", value);
}
} else if (key.startsWith("$") || key.startsWith("data-")) {
const dataKey = key.startsWith("$") ? key.substring(1) : key.startsWith("data-") ? key.substring(5) : key;
if (!(dataKey in this.args)) {
this.args[dataKey] = value;
this.$.data(dataKey, value);
this.$.attr(key.startsWith("$") ? `data-${dataKey}` : key, String(value));
}
} else {
if (!this.$.attr(key)) {
this.$.attr(key, value);
}
}
}
}
}
_set_attributes() {
this.$.attr("data-cid", this._cid);
if (window.jqhtml?.debug?.verbose) {
this.$.attr("data-_lifecycle-state", this._ready_state.toString());
}
}
_update_debug_attrs() {
if (window.jqhtml?.debug?.verbose) {
this.$.attr("data-_lifecycle-state", this._ready_state.toString());
}
}
_find_dom_parent() {
let current = this.$.parent();
while (current.length > 0) {
const parent = current.data("_component");
if (parent instanceof _Jqhtml_Component) {
this._dom_parent = parent;
parent._dom_children.add(this);
break;
}
current = current.parent();
}
}
/**
* Get DOM children (components in DOM subtree)
* Uses fast _dom_children registry when possible, falls back to DOM traversal for off-DOM components
* @private - Used internally for lifecycle coordination
*/
_get_dom_children() {
if (this._use_dom_fallback) {
const directChildren = [];
this.$.find(".Component").each((_, el) => {
const $el = $(el);
const comp = $el.data("_component");
if (comp instanceof _Jqhtml_Component) {
const closestParent = $el.parent().closest(".Component");
if (closestParent.length === 0 || closestParent.data("_component") === this) {
directChildren.push(comp);
}
}
});
return directChildren;
}
const children = Array.from(this._dom_children);
return children.filter((child) => {
return $.contains(document.documentElement, child.$[0]);
});
}
_log_lifecycle(phase, status) {
logLifecycle(this, phase, status);
if (typeof window !== "undefined" && window.JQHTML_DEBUG) {
window.JQHTML_DEBUG.log(this.component_name(), phase, status, {
cid: this._cid,
ready_state: this._ready_state,
args: this.args
});
}
}
_log_debug(action, ...args) {
if (typeof window !== "undefined" && window.JQHTML_DEBUG) {
window.JQHTML_DEBUG.log(this.component_name(), "debug", `${action}: ${args.map((a) => JSON.stringify(a)).join(", ")}`);
}
}
};
async function process_slot_inheritance(component, childSlots) {
let currentClass = Object.getPrototypeOf(component.constructor);
console.log(`[JQHTML] Walking prototype chain for ${component.constructor.name}`);
while (currentClass && currentClass !== Component && currentClass.name !== "Object") {
const className = currentClass.name;
console.log(`[JQHTML] Checking parent class: ${className}`);
if (className === "_Jqhtml_Component" || className === "_Base_Jqhtml_Component") {
currentClass = Object.getPrototypeOf(currentClass);
continue;
}
try {
const parentTemplate = get_template(className);
console.log(`[JQHTML] Template found for ${className}:`, parentTemplate ? parentTemplate.name : "null");
if (parentTemplate && parentTemplate.name !== "Component") {
console.log(`[JQHTML] Invoking parent template ${className}`);
const [parentInstructions, parentContext] = parentTemplate.render.call(
component,
component.data,
component.args,
childSlots
// Pass child slots as content parameter
);
if (parentInstructions && typeof parentInstructions === "object" && parentInstructions._slots) {
console.log(`[JQHTML] Parent also slot-only, recursing`);
return await process_slot_inheritance(component, parentInstructions._slots);
}
console.log(`[JQHTML] Parent returned instructions, inheritance complete`);
return [parentInstructions, parentContext];
}
} catch (error) {
console.warn(`[JQHTML] Error looking up parent template for ${className}:`, error);
}
currentClass = Object.getPrototypeOf(currentClass);
}
console.warn(`[JQHTML] No parent template found after walking chain`);
return null;
}
async function render_template(component, template_fn) {
let render_fn = template_fn;
if (!render_fn) {
const template_def = get_template_by_class(component.constructor);
render_fn = template_def.render;
}
if (!render_fn) {
return;
}
component.$.empty();
const defaultContent = () => "";
let [instructions, context] = render_fn.call(
component,
component.data,
component.args,
defaultContent
// Default content function that returns empty string
);
if (instructions && typeof instructions === "object" && instructions._slots) {
console.log(`[JQHTML] Slot-only template detected for ${component.constructor.name}, invoking inheritance`);
const result = await process_slot_inheritance(component, instructions._slots);
if (result) {
console.log(`[JQHTML] Parent template found, using parent instructions`);
instructions = result[0];
context = result[1];
} else {
console.warn(`[JQHTML] No parent template found for ${component.constructor.name}, rendering empty`);
instructions = [];
}
}
await process_instructions(instructions, component.$, component);
await process_bindings(component);
await attach_event_handlers(component);
}
async function process_bindings(component) {
component.$.find("[data-bind-prop], [data-bind-value], [data-bind-text], [data-bind-html], [data-bind-class], [data-bind-style]").each((_, element) => {
const el = $(element);
const attrs = element.attributes;
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name.startsWith("data-bind-")) {
const binding_type = attr.name.substring(10);
const expression = attr.value;
try {
const value = evaluate_expression(expression, component);
switch (binding_type) {
case "prop":
const prop_name = el.attr("data-bind-prop-name") || "value";
el.prop(prop_name, value);
break;
case "value":
el.val(value);
break;
case "text":
el.text(value);
break;
case "html":
el.html(value);
break;
case "class":
if (typeof value === "object") {
Object.entries(value).forEach(([className, enabled]) => {
el.toggleClass(className, !!enabled);
});
} else {
el.addClass(String(value));
}
break;
case "style":
if (typeof value === "object") {
el.css(value);
} else {
el.attr("style", String(value));
}
break;
default:
el.attr(binding_type, value);
}
} catch (error) {
console.error(`Error evaluating binding "${expression}":`, error);
}
}
}
});
}
async function attach_event_handlers(component) {
component.$.find("[data-on-click], [data-on-change], [data-on-submit], [data-on-keyup], [data-on-keydown], [data-on-focus], [data-on-blur]").each((_, element) => {
const el = $(element);
const attrs = element.attributes;
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name.startsWith("data-on-")) {
const event_name = attr.name.substring(8);
const handler_expr = attr.value;
el.removeAttr(attr.name);
el.on(event_name, function(event) {
try {
const handler = evaluate_handler(handler_expr, component);
if (typeof handler === "function") {
handler.call(component, event);
} else {
evaluate_expression(handler_expr, component, { $event: event });
}
} catch (error) {
console.error(`Error in ${event_name} handler "${handler_expr}":`, error);
}
});
}
}
});
}
function evaluate_expression(expression, component, locals = {}) {
const context = {
// Component properties
data: component.data,
args: component.args,
$: component.$,
// Component methods
emit: component.emit.bind(component),
$id: component.$id.bind(component),
// Locals (like $event)
...locals
};
const keys = Object.keys(context);
const values = Object.values(context);
try {
const fn = new Function(...keys, `return (${expression})`);
return fn(...values);
} catch (error) {
console.error(`Invalid expression: ${expression}`, error);
return void 0;
}
}
function evaluate_handler(expression, component) {
if (expression in component && typeof component[expression] === "function") {
return component[expression];
}
try {
return new Function("$event", `
const { data, args, $, emit, $id } = this;
${expression}
`).bind(component);
} catch (error) {
console.error(`Invalid handler: ${expression}`, error);
return null;
}
}
function escape_html(str) {
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
function getJQuery() {
if (typeof window !== "undefined" && window.$) {
return window.$;
}
if (typeof window !== "undefined" && window.jQuery) {
return window.jQuery;
}
throw new Error('FATAL: jQuery is not defined. jQuery must be loaded before using JQHTML. Add