Files
rspade_system/storage-broken/rsx-build/bundles/npm_Jquery_Bundle_6459e8ed0f60bda4f121420766012d53.js
root 78553d4edf Fix code quality violations for publish
Remove unused blade settings pages not linked from UI
Convert remaining frontend pages to SPA actions
Convert settings user_settings and general to SPA actions
Convert settings profile pages to SPA actions
Convert contacts and projects add/edit pages to SPA actions
Convert clients add/edit page to SPA action with loading pattern
Refactor component scoped IDs from $id to $sid
Fix jqhtml comment syntax and implement universal error component system
Update all application code to use new unified error system
Remove all backwards compatibility - unified error system complete
Phase 5: Remove old response classes
Phase 3-4: Ajax response handler sends new format, old helpers deprecated
Phase 2: Add client-side unified error foundation
Phase 1: Add server-side unified error foundation
Add unified Ajax error response system with constants

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 04:35:01 +00:00

2204 lines
82 KiB
JavaScript
Executable File

(() => {
// 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, "&quot;");
html.push(` ${key}="${escaped_value}"`);
} else if (typeof value === "boolean" && value) {
html.push(` ${key}`);
}
}
html.push(">");
const escaped_content = rawContent.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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: <button $id="save_btn">Save</button>
* Rendered: <button id="save_btn:abc123" data-id="save_btn">Save</button>
* 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: <User_Card $id="active_user" />
* 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 <script src="https://code.jquery.com/jquery-3.7.1.min.js"><\/script> before loading JQHTML.');
}
function getJqhtml() {
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 JQHTML components. Ensure @jqhtml/core is imported and initialized before attempting to use debug features.");
}
var DebugOverlay = class _DebugOverlay {
constructor(options = {}) {
this.$container = null;
this.$statusIndicator = null;
this.$ = getJQuery();
if (!this.$) {
throw new Error("jQuery is required for DebugOverlay");
}
this.options = {
position: "bottom",
theme: "dark",
compact: false,
showStatus: true,
autoHide: false,
...options
};
}
/**
* Static method to show debug overlay (singleton pattern)
*/
static show(options) {
if (!_DebugOverlay.instance) {
_DebugOverlay.instance = new _DebugOverlay(options);
}
_DebugOverlay.instance.display();
return _DebugOverlay.instance;
}
/**
* Static method to hide debug overlay
*/
static hide() {
if (_DebugOverlay.instance) {
_DebugOverlay.instance.hide();
}
}
/**
* Static method to toggle debug overlay visibility
*/
static toggle() {
if (_DebugOverlay.instance && _DebugOverlay.instance.$container) {
if (_DebugOverlay.instance.$container.is(":visible")) {
_DebugOverlay.hide();
} else {
_DebugOverlay.instance.display();
}
} else {
_DebugOverlay.show();
}
}
/**
* Static method to destroy debug overlay
*/
static destroy() {
if (_DebugOverlay.instance) {
_DebugOverlay.instance.destroy();
_DebugOverlay.instance = null;
}
}
/**
* Display the debug overlay
*/
display() {
if (this.$container) {
this.$container.show();
return;
}
this.createOverlay();
if (this.options.showStatus) {
this.createStatusIndicator();
}
}
/**
* Hide the debug overlay
*/
hide() {
if (this.$container) {
this.$container.hide();
}
if (this.$statusIndicator) {
this.$statusIndicator.hide();
}
}
/**
* Remove the debug overlay completely
*/
destroy() {
if (this.$container) {
this.$container.remove();
this.$container = null;
}
if (this.$statusIndicator) {
this.$statusIndicator.remove();
this.$statusIndicator = null;
}
}
/**
* Update the status indicator
*/
updateStatus(mode) {
if (!this.$statusIndicator)
return;
this.$statusIndicator.text("Debug: " + mode);
this.$statusIndicator.attr("class", "jqhtml-debug-status" + (mode !== "Off" ? " active" : ""));
}
createOverlay() {
this.addStyles();
this.$container = this.$("<div>").addClass(`jqhtml-debug-overlay ${this.options.theme} ${this.options.position}`);
const $content = this.$("<div>").addClass("jqhtml-debug-content");
const $controls = this.$("<div>").addClass("jqhtml-debug-controls");
const $title = this.$("<span>").addClass("jqhtml-debug-title").html("<strong>\u{1F41B} JQHTML Debug:</strong>");
$controls.append($title);
const buttons = [
{ text: "Slow Motion + Flash", action: "enableSlowMotionDebug", class: "success" },
{ text: "Basic Debug", action: "enableBasicDebug", class: "" },
{ text: "Full Debug", action: "enableFullDebug", class: "" },
{ text: "Sequential", action: "enableSequentialMode", class: "" },
{ text: "Clear Debug", action: "clearAllDebug", class: "danger" },
{ text: "Settings", action: "showDebugInfo", class: "" }
];
buttons.forEach((btn) => {
const $button = this.$("<button>").text(btn.text).addClass("jqhtml-debug-btn" + (btn.class ? ` ${btn.class}` : "")).on("click", () => this.executeAction(btn.action));
$controls.append($button);
});
const $toggleBtn = this.$("<button>").text(this.options.compact ? "\u25BC" : "\u25B2").addClass("jqhtml-debug-toggle").on("click", () => this.toggle());
$controls.append($toggleBtn);
$content.append($controls);
this.$container.append($content);
this.$("body").append(this.$container);
}
createStatusIndicator() {
this.$statusIndicator = this.$("<div>").addClass("jqhtml-debug-status").text("Debug: Off").css({
position: "fixed",
top: "10px",
right: "10px",
background: "#2c3e50",
color: "white",
padding: "5px 10px",
borderRadius: "4px",
fontSize: "0.75rem",
zIndex: "10001",
opacity: "0.8",
fontFamily: "monospace"
});
this.$("body").append(this.$statusIndicator);
}
addStyles() {
if (this.$("#jqhtml-debug-styles").length > 0)
return;
const $style = this.$("<style>").attr("id", "jqhtml-debug-styles").text('.jqhtml-debug-overlay {position: fixed;left: 0;right: 0;z-index: 10000;font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;font-size: 0.8rem;box-shadow: 0 2px 10px rgba(0,0,0,0.2);}.jqhtml-debug-overlay.top {top: 0;}.jqhtml-debug-overlay.bottom {bottom: 0;}.jqhtml-debug-overlay.dark {background: #34495e;color: #ecf0f1;}.jqhtml-debug-overlay.light {background: #f8f9fa;color: #333;border-bottom: 1px solid #dee2e6;}.jqhtml-debug-content {padding: 0.5rem 1rem;}.jqhtml-debug-controls {display: flex;flex-wrap: wrap;gap: 8px;align-items: center;}.jqhtml-debug-title {margin-right: 10px;font-weight: bold;}.jqhtml-debug-btn {padding: 4px 8px;border: none;border-radius: 3px;background: #3498db;color: white;cursor: pointer;font-size: 0.75rem;transition: background 0.2s;}.jqhtml-debug-btn:hover {background: #2980b9;}.jqhtml-debug-btn.success {background: #27ae60;}.jqhtml-debug-btn.success:hover {background: #229954;}.jqhtml-debug-btn.danger {background: #e74c3c;}.jqhtml-debug-btn.danger:hover {background: #c0392b;}.jqhtml-debug-toggle {padding: 4px 8px;border: none;border-radius: 3px;background: #7f8c8d;color: white;cursor: pointer;font-size: 0.75rem;margin-left: auto;}.jqhtml-debug-toggle:hover {background: #6c7b7d;}.jqhtml-debug-status.active {background: #27ae60 !important;}@media (max-width: 768px) {.jqhtml-debug-controls {flex-direction: column;align-items: flex-start;}.jqhtml-debug-title {margin-bottom: 5px;}}');
this.$("head").append($style);
}
toggle() {
this.options.compact = !this.options.compact;
const $toggleBtn = this.$container.find(".jqhtml-debug-toggle");
$toggleBtn.text(this.options.compact ? "\u25BC" : "\u25B2");
const $buttons = this.$container.find(".jqhtml-debug-btn");
if (this.options.compact) {
$buttons.hide();
} else {
$buttons.show();
}
}
executeAction(action) {
const jqhtml2 = getJqhtml();
if (!jqhtml2) {
console.warn("JQHTML not available - make sure it's loaded and exposed globally");
return;
}
switch (action) {
case "enableSlowMotionDebug":
jqhtml2.setDebugSettings({
logFullLifecycle: true,
sequentialProcessing: true,
delayAfterComponent: 150,
delayAfterRender: 200,
delayAfterRerender: 250,
flashComponents: true,
flashDuration: 800,
flashColors: {
create: "#3498db",
render: "#27ae60",
ready: "#9b59b6"
},
profilePerformance: true,
highlightSlowRenders: 30,
logDispatch: true
});
this.updateStatus("Slow Motion");
console.log("\u{1F41B} Slow Motion Debug Mode Enabled");
break;
case "enableBasicDebug":
jqhtml2.enableDebugMode("basic");
this.updateStatus("Basic");
console.log("\u{1F41B} Basic Debug Mode Enabled");
break;
case "enableFullDebug":
jqhtml2.enableDebugMode("full");
this.updateStatus("Full");
console.log("\u{1F41B} Full Debug Mode Enabled");
break;
case "enableSequentialMode":
jqhtml2.setDebugSettings({
logCreationReady: true,
sequentialProcessing: true,
flashComponents: true,
profilePerformance: true
});
this.updateStatus("Sequential");
console.log("\u{1F41B} Sequential Processing Mode Enabled");
break;
case "clearAllDebug":
jqhtml2.clearDebugSettings();
this.updateStatus("Off");
console.log("\u{1F41B} All Debug Modes Disabled");
break;
case "showDebugInfo":
const settings = JSON.stringify(jqhtml2.debug, null, 2);
console.log("\u{1F41B} Current Debug Settings:", settings);
alert("Debug settings logged to console:\n\n" + (Object.keys(jqhtml2.debug).length > 0 ? settings : "No debug settings active"));
break;
}
}
};
DebugOverlay.instance = null;
if (typeof window !== "undefined") {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("debug") === "true" || urlParams.get("jqhtml-debug") === "true") {
document.addEventListener("DOMContentLoaded", () => {
DebugOverlay.show();
});
}
}
function init_jquery_plugin(jQuery) {
if (!jQuery || !jQuery.fn) {
throw new Error("jQuery is required for JQHTML. Please ensure jQuery is loaded before initializing JQHTML.");
}
if (typeof window !== "undefined" && window.$ !== jQuery && !jQuery.__jqhtml_checked) {
devWarn('jQuery instance appears to be bundled with webpack/modules rather than loaded globally.\nFor best compatibility, it is recommended to:\n1. Include jQuery via <script> tag from a CDN (UMD format)\n2. Configure webpack with: externals: { jquery: "$" }\n3. Remove jquery from package.json dependencies\n\nTo suppress this warning, set: window.JQHTML_SUPPRESS_WARNINGS = true');
jQuery.__jqhtml_checked = true;
}
const _jqhtml_original_jquery = jQuery;
const JQueryWithComponentSupport = function(selector, context) {
if (selector && typeof selector === "object" && selector.$ && typeof selector.$id === "function" && typeof selector.id === "function") {
return selector.$;
}
return new _jqhtml_original_jquery(selector, context);
};
Object.setPrototypeOf(JQueryWithComponentSupport, _jqhtml_original_jquery);
for (const key in _jqhtml_original_jquery) {
if (_jqhtml_original_jquery.hasOwnProperty(key)) {
JQueryWithComponentSupport[key] = _jqhtml_original_jquery[key];
}
}
JQueryWithComponentSupport.prototype = _jqhtml_original_jquery.prototype;
JQueryWithComponentSupport.fn = _jqhtml_original_jquery.fn;
if (typeof window !== "undefined") {
window.jQuery = JQueryWithComponentSupport;
window.$ = JQueryWithComponentSupport;
}
jQuery = JQueryWithComponentSupport;
const originalVal = jQuery.fn.val;
jQuery.fn.val = function(value) {
if (arguments.length === 0) {
const firstEl = this.first();
if (firstEl.length === 0)
return void 0;
const component = firstEl.data("_component");
const tagName = firstEl.prop("tagName");
if (component && typeof component.val === "function" && tagName !== "INPUT" && tagName !== "TEXTAREA") {
return component.val();
}
return originalVal.call(this);
} else {
this.each(function() {
const $el = jQuery(this);
const component = $el.data("_component");
const tagName = $el.prop("tagName");
if (component && typeof component.val === "function" && tagName !== "INPUT" && tagName !== "TEXTAREA") {
component.val(value);
} else {
originalVal.call($el, value);
}
});
return this;
}
};
jQuery.fn.component = function(componentOrName, args = {}) {
const element = this.first ? this.first() : this;
if (!componentOrName) {
if (element.length === 0) {
return null;
}
const comp = element.data("_component");
return comp || null;
}
const existingComponent = element.data("_component");
if (existingComponent) {
return existingComponent;
}
let ComponentClass;
let componentName;
if (typeof componentOrName === "string") {
componentName = componentOrName;
const found = get_component_class(componentOrName);
args = { ...args, _component_name: componentName };
if (!found) {
ComponentClass = Component;
} else {
ComponentClass = found;
}
} else {
ComponentClass = componentOrName;
}
let targetElement = element;
if (componentName) {
const template = get_template(componentName);
const expectedTag = args._tag || template.tag || "div";
const currentTag = element.prop("tagName").toLowerCase();
if (currentTag !== expectedTag.toLowerCase()) {
if (args._inner_html) {
const newElement = jQuery(`<${expectedTag}></${expectedTag}>`);
const oldEl = element[0];
if (oldEl && oldEl.attributes) {
for (let i = 0; i < oldEl.attributes.length; i++) {
const attr = oldEl.attributes[i];
newElement.attr(attr.name, attr.value);
}
}
newElement.html(element.html());
element.replaceWith(newElement);
targetElement = newElement;
} else {
console.warn(`[JQHTML] Component '${componentName}' expects tag '<${expectedTag}>' but element is '<${currentTag}>'. Element tag will not be changed. Consider using the correct tag.`);
}
}
}
const component = new ComponentClass(targetElement, args);
component.boot();
applyDebugDelay("component");
return component;
};
const _jqhtml_jquery_overrides = {};
const dom_insertion_methods = ["append", "prepend", "before", "after", "replaceWith"];
for (const fnname of dom_insertion_methods) {
_jqhtml_jquery_overrides[fnname] = jQuery.fn[fnname];
jQuery.fn[fnname] = function(...args) {
const resolvedArgs = args.map((arg) => {
if (arg && typeof arg === "object" && arg instanceof Component) {
return arg.$;
}
return arg;
});
const $elements = resolvedArgs.filter((arg) => arg instanceof jQuery);
const ret = _jqhtml_jquery_overrides[fnname].apply(this, resolvedArgs);
for (const $e of $elements) {
if ($e.closest("html").length > 0) {
$e.find(".Component").addBack(".Component").each(function() {
const $comp = jQuery(this);
const component = $comp.data("_component");
if (component && !component._ready_state) {
component.boot();
}
});
}
}
return ret;
};
}
jQuery.fn.shallowFind = function(selector) {
const results = [];
this.each(function() {
const traverse = (parent) => {
for (let i = 0; i < parent.children.length; i++) {
const child = parent.children[i];
if (jQuery(child).is(selector)) {
results.push(child);
} else {
traverse(child);
}
}
};
traverse(this);
});
return jQuery(results);
};
const originalEmpty = jQuery.fn.empty;
const originalHtml = jQuery.fn.html;
const originalText = jQuery.fn.text;
jQuery.fn.empty = function() {
return this.each(function() {
jQuery(this).find(".Component").each(function() {
const component = jQuery(this).data("_component");
if (component && !component._stopped) {
component._stop();
}
});
originalEmpty.call(jQuery(this));
});
};
jQuery.fn.html = function(value) {
if (arguments.length === 0) {
return originalHtml.call(this);
}
return this.each(function() {
jQuery(this).empty();
originalHtml.call(jQuery(this), value);
});
};
jQuery.fn.text = function(value) {
if (arguments.length === 0) {
return originalText.call(this);
}
return this.each(function() {
jQuery(this).empty();
originalText.call(jQuery(this), value);
});
};
}
if (typeof window !== "undefined" && window.jQuery) {
init_jquery_plugin(window.jQuery);
}
var version = "2.2.185";
var jqhtml = {
// Core
Component,
LifecycleManager,
// Registry
register_component,
get_component_class,
register_template,
get_template,
get_template_by_class,
create_component,
has_component,
get_component_names,
get_registered_templates,
list_components,
// Template system
process_instructions,
extract_slots,
render_template,
escape_html,
// Version property - internal
__version: version,
// Debug settings
debug: {
enabled: false,
verbose: false
},
// Debug helper functions (mainly for internal use but exposed for advanced debugging)
setDebugSettings(settings) {
Object.assign(this.debug, settings);
},
enableDebugMode(level = "basic") {
if (level === "basic") {
this.debug.logCreationReady = true;
this.debug.logDispatch = true;
this.debug.flashComponents = true;
} else {
this.debug.logFullLifecycle = true;
this.debug.logDispatchVerbose = true;
this.debug.flashComponents = true;
this.debug.profilePerformance = true;
this.debug.traceDataFlow = true;
}
},
clearDebugSettings() {
this.debug = {};
},
// Debug overlay methods
showDebugOverlay(options) {
return DebugOverlay.show(options);
},
hideDebugOverlay() {
return DebugOverlay.hide();
},
// Export DebugOverlay class for direct access
DebugOverlay,
// Install globals function
installGlobals() {
if (typeof window !== "undefined") {
window.jqhtml = this;
window.Component = Component;
window.Jqhtml_LifecycleManager = LifecycleManager;
}
},
// Version display function - shows version of core library and all registered templates
_version() {
console.log(`JQHTML Core v${this.__version}`);
console.log("Registered Templates:");
const templateNames = get_component_names();
if (templateNames.length === 0) {
console.log(" (no templates registered)");
} else {
for (const name of templateNames) {
const template = get_template(name);
const templateVersion = template ? template._jqhtml_version || "unknown" : "unknown";
console.log(` - ${name}: v${templateVersion}`);
}
}
return this.__version;
},
// Public version function - returns the stamped version number
version() {
return version;
}
};
if (typeof window !== "undefined" && !window.jqhtml) {
window.jqhtml = jqhtml;
window.Component = Component;
window.Component = Component;
window.Jqhtml_LifecycleManager = LifecycleManager;
if (jqhtml.debug?.enabled) {
console.log("[JQHTML] Auto-registered window.jqhtml global for template compatibility");
}
}
// storage/rsx-tmp/npm-compile/entry_6459e8ed0f60bda4f121420766012d53.js
window._rsx_npm = window._rsx_npm || {};
window._rsx_npm.jqhtml = jqhtml;
window._rsx_npm._Base_Jqhtml_Component = Component;
})();