Enhance refactor commands with controller-aware Route() updates and fix code quality violations
Add semantic token highlighting for 'that' variable and comment file references in VS Code extension Add Phone_Text_Input and Currency_Input components with formatting utilities Implement client widgets, form standardization, and soft delete functionality Add modal scroll lock and update documentation Implement comprehensive modal system with form integration and validation Fix modal component instantiation using jQuery plugin API Implement modal system with responsive sizing, queuing, and validation support Implement form submission with validation, error handling, and loading states Implement country/state selectors with dynamic data loading and Bootstrap styling Revert Rsx::Route() highlighting in Blade/PHP files Target specific PHP scopes for Rsx::Route() highlighting in Blade Expand injection selector for Rsx::Route() highlighting Add custom syntax highlighting for Rsx::Route() and Rsx.Route() calls Update jqhtml packages to v2.2.165 Add bundle path validation for common mistakes (development mode only) Create Ajax_Select_Input widget and Rsx_Reference_Data controller Create Country_Select_Input widget with default country support Initialize Tom Select on Select_Input widgets Add Tom Select bundle for enhanced select dropdowns Implement ISO 3166 geographic data system for country/region selection Implement widget-based form system with disabled state support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
270
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
270
node_modules/@jqhtml/parser/dist/codegen.js
generated
vendored
@@ -10,6 +10,7 @@ export class CodeGenerator {
|
||||
current_component = null;
|
||||
in_slot = false;
|
||||
tag_depth = 0;
|
||||
lastOutput = ''; // Track last generated output for deduplication
|
||||
// Position tracking for source maps
|
||||
outputLine = 1;
|
||||
outputColumn = 0;
|
||||
@@ -178,6 +179,7 @@ export class CodeGenerator {
|
||||
}
|
||||
generate_component(node) {
|
||||
this.current_component = node.name;
|
||||
this.lastOutput = ''; // Reset output tracking for each component
|
||||
const dependencies = new Set();
|
||||
// Always use 1:1 line mapping for proper sourcemaps
|
||||
// Even when using SourceMapGenerator, we need the line structure
|
||||
@@ -234,8 +236,11 @@ export class CodeGenerator {
|
||||
slots[slotNode.name] = slotNode;
|
||||
}
|
||||
}
|
||||
// Filter out TEXT nodes (whitespace) from slot-only templates
|
||||
// TEXT nodes cause _output.push() calls which are invalid in slot-only context
|
||||
const slotsOnly = node.body.filter(child => child.type === NodeType.SLOT);
|
||||
// Use 1:1 line mapping for slot-only templates
|
||||
const bodyLines = this.generate_function_body_1to1(node.body);
|
||||
const bodyLines = this.generate_function_body_1to1(slotsOnly);
|
||||
// Build the render function with line preservation
|
||||
const lines = [];
|
||||
// Line 1: function declaration returning slots object
|
||||
@@ -387,39 +392,89 @@ export class CodeGenerator {
|
||||
// Generate code based on node type
|
||||
switch (node.type) {
|
||||
case NodeType.HTML_TAG: {
|
||||
this.lastOutput = ''; // Reset for non-text output
|
||||
const tag = node;
|
||||
// Opening tag goes on its line
|
||||
const openTag = this.generate_tag_open(tag);
|
||||
if (openTag) {
|
||||
lines[lineIndex] = (lines[lineIndex] || '') + openTag;
|
||||
// Check if this is a raw content tag (textarea, pre)
|
||||
if (tag.preserveWhitespace && !tag.selfClosing && tag.children && tag.children.length > 0) {
|
||||
// Validate: only TEXT, EXPRESSION, and CODE_BLOCK children allowed in raw content tags
|
||||
// HTML tags and components are not allowed (they would break whitespace preservation)
|
||||
for (const child of tag.children) {
|
||||
if (child.type !== NodeType.TEXT &&
|
||||
child.type !== NodeType.EXPRESSION &&
|
||||
child.type !== NodeType.CODE_BLOCK) {
|
||||
const error = new JQHTMLParseError(`Invalid content in <${tag.name}> tag`, tag.line, tag.column || 0, this.sourceContent, this.sourceFile);
|
||||
error.suggestion =
|
||||
`\n\nAll content within <textarea> and <pre> tags must be plain text or expressions.\n` +
|
||||
`HTML tags and components are not allowed.\n\n` +
|
||||
`Allowed:\n` +
|
||||
` <textarea><%= this.data.value %></textarea> ← expressions OK\n` +
|
||||
` <textarea>plain text</textarea> ← plain text OK\n\n` +
|
||||
`Not allowed:\n` +
|
||||
` <textarea><div>content</div></textarea> ← HTML tags not OK\n` +
|
||||
` <textarea><MyComponent /></textarea> ← components not OK\n\n` +
|
||||
`This ensures proper whitespace preservation.`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// Generate rawtag instruction with raw content
|
||||
const attrs_obj = this.generate_attributes_with_conditionals(tag.attributes, tag.conditionalAttributes);
|
||||
// Collect raw content from children (all validated as TEXT)
|
||||
let rawContent = '';
|
||||
for (const child of tag.children) {
|
||||
rawContent += child.content;
|
||||
}
|
||||
// Escape the raw content for JavaScript string
|
||||
const escapedContent = this.escape_string(rawContent);
|
||||
const rawtagInstruction = `_output.push({rawtag: ["${tag.name}", ${attrs_obj}, ${escapedContent}]});`;
|
||||
lines[lineIndex] = (lines[lineIndex] || '') + rawtagInstruction;
|
||||
}
|
||||
// Process children
|
||||
if (tag.children) {
|
||||
tag.children.forEach(processNodeForLine);
|
||||
}
|
||||
// Closing tag might be on a different line
|
||||
const closeTag = `_output.push("</${tag.name}>");`;
|
||||
// For simplicity, put closing tag on the last child's line or same line
|
||||
const closeLine = tag.children && tag.children.length > 0
|
||||
? (tag.children[tag.children.length - 1].line || node.line)
|
||||
: node.line;
|
||||
const closeIndex = closeLine - 2;
|
||||
if (closeIndex >= 0 && closeIndex < lines.length) {
|
||||
lines[closeIndex] = (lines[closeIndex] || '') + ' ' + closeTag;
|
||||
else {
|
||||
// Normal HTML tag processing
|
||||
// Opening tag goes on its line
|
||||
const openTag = this.generate_tag_open(tag);
|
||||
if (openTag) {
|
||||
lines[lineIndex] = (lines[lineIndex] || '') + openTag;
|
||||
}
|
||||
// Process children
|
||||
if (tag.children) {
|
||||
tag.children.forEach(processNodeForLine);
|
||||
}
|
||||
// Closing tag might be on a different line
|
||||
const closeTag = `_output.push("</${tag.name}>");`;
|
||||
// For simplicity, put closing tag on the last child's line or same line
|
||||
const closeLine = tag.children && tag.children.length > 0
|
||||
? (tag.children[tag.children.length - 1].line || node.line)
|
||||
: node.line;
|
||||
const closeIndex = closeLine - 2;
|
||||
if (closeIndex >= 0 && closeIndex < lines.length) {
|
||||
lines[closeIndex] = (lines[closeIndex] || '') + ' ' + closeTag;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NodeType.TEXT: {
|
||||
const text = node;
|
||||
// Only generate code for non-whitespace text
|
||||
if (text.content.trim()) {
|
||||
const code = `_output.push(${this.escape_string(text.content)});`;
|
||||
// Apply padded trim to preserve intentional whitespace
|
||||
const processed = this.padded_trim(text.content);
|
||||
if (processed) {
|
||||
const code = `_output.push(${this.escape_string(processed)});`;
|
||||
// Optimization: skip consecutive identical space pushes
|
||||
if (code === '_output.push(" ");' && this.lastOutput === '_output.push(" ");') {
|
||||
// Skip duplicate - don't add to output
|
||||
break;
|
||||
}
|
||||
this.lastOutput = code; // Track for next comparison
|
||||
lines[lineIndex] = (lines[lineIndex] || '') + ' ' + code;
|
||||
}
|
||||
// Whitespace-only text nodes don't generate code but preserve line positioning
|
||||
else {
|
||||
// Empty text resets tracking so next space won't be skipped
|
||||
this.lastOutput = '';
|
||||
}
|
||||
// Empty after processing: skip (no code generated)
|
||||
break;
|
||||
}
|
||||
case NodeType.CODE_BLOCK: {
|
||||
this.lastOutput = ''; // Reset for non-text output
|
||||
const codeBlock = node;
|
||||
const code = this.generate_code_block(codeBlock);
|
||||
if (code) {
|
||||
@@ -440,11 +495,16 @@ export class CodeGenerator {
|
||||
break;
|
||||
}
|
||||
case NodeType.EXPRESSION: {
|
||||
this.lastOutput = ''; // Reset for non-text output
|
||||
const expr = node;
|
||||
// Generate the expression wrapper on a single line
|
||||
let code;
|
||||
// Special handling for content() calls
|
||||
const trimmedCode = expr.code.trim();
|
||||
// Strip trailing semicolon if present (optional in <%= %> blocks)
|
||||
let trimmedCode = expr.code.trim();
|
||||
if (trimmedCode.endsWith(';')) {
|
||||
trimmedCode = trimmedCode.slice(0, -1).trim();
|
||||
}
|
||||
if (trimmedCode === 'content()') {
|
||||
// Default slot/content - check _inner_html first
|
||||
code = `(() => { if (this.args._inner_html) { _output.push(this.args._inner_html); } else if (typeof content === 'function') { const [contentInstructions] = content.call(this); _output.push(['_content', contentInstructions]); } })();`;
|
||||
@@ -456,17 +516,8 @@ export class CodeGenerator {
|
||||
}
|
||||
else if (trimmedCode.match(/^content\s*\(\s*['"]([^'"]+)['"]\s*(?:,\s*(.+?))?\s*\)$/)) {
|
||||
// Named slot: content('header') or content('header', data) (function call with string parameter and optional data)
|
||||
const match = trimmedCode.match(/^content\s*\(\s*['"]([^'"]+)['"]\s*(?:,\s*(.+?))?\s*\)$/);
|
||||
const slotName = match[1];
|
||||
const dataParam = match[2];
|
||||
if (dataParam) {
|
||||
// With data parameter: content('row', record) -> pass data to slot function
|
||||
code = `(() => { if (typeof content === 'object' && typeof content.${slotName} === 'function') { const [contentInstructions] = content.${slotName}.call(this, ${dataParam}); _output.push(['_content', contentInstructions]); } })();`;
|
||||
}
|
||||
else {
|
||||
// Without data parameter: content('header') -> pass undefined
|
||||
code = `(() => { if (typeof content === 'object' && typeof content.${slotName} === 'function') { const [contentInstructions] = content.${slotName}.call(this); _output.push(['_content', contentInstructions]); } })();`;
|
||||
}
|
||||
// Use the standard result pattern for proper handling
|
||||
code = `(() => { const result = ${trimmedCode};; if (Array.isArray(result)) { if (result.length === 2 && Array.isArray(result[0])) { _output.push(...result[0]); } else { _output.push(...result); } } else { _output.push(jqhtml.escape_html(result)); } })();`;
|
||||
}
|
||||
else if (expr.escaped) {
|
||||
code = `(() => { const result = ${expr.code}; if (Array.isArray(result)) { if (result.length === 2 && Array.isArray(result[0])) { _output.push(...result[0]); } else { _output.push(...result); } } else { _output.push(jqhtml.escape_html(result)); } })();`;
|
||||
@@ -481,9 +532,10 @@ export class CodeGenerator {
|
||||
break;
|
||||
}
|
||||
case NodeType.COMPONENT_INVOCATION: {
|
||||
this.lastOutput = ''; // Reset for non-text output
|
||||
const comp = node;
|
||||
// For 1:1 mapping, generate compact component invocations
|
||||
const attrs = this.generate_attributes_object(comp.attributes);
|
||||
const attrs = this.generate_attributes_with_conditionals(comp.attributes, comp.conditionalAttributes);
|
||||
if (comp.selfClosing || comp.children.length === 0) {
|
||||
// Simple component without children
|
||||
const code = `_output.push({comp: ["${comp.name}", ${attrs}]});`;
|
||||
@@ -621,15 +673,44 @@ export class CodeGenerator {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Padded trim: Collapse internal whitespace but preserve leading/trailing space
|
||||
* Examples:
|
||||
* " hello " → " hello "
|
||||
* "hello" → "hello"
|
||||
* " " → " "
|
||||
* "\n\n \n" → " "
|
||||
*/
|
||||
padded_trim(text) {
|
||||
const has_leading_space = /^\s/.test(text);
|
||||
const has_trailing_space = /\s$/.test(text);
|
||||
// Trim the text
|
||||
let result = text.trim();
|
||||
// Add back single space if original had leading/trailing whitespace
|
||||
if (has_leading_space)
|
||||
result = ' ' + result;
|
||||
if (has_trailing_space)
|
||||
result = result + ' ';
|
||||
// Final pass: collapse all whitespace sequences to single space
|
||||
return result.replace(/\s+/g, ' ');
|
||||
}
|
||||
generate_text(node) {
|
||||
const content = node.content;
|
||||
// Skip empty text nodes (just whitespace)
|
||||
if (content.trim() === '') {
|
||||
// Apply padded trim to preserve intentional whitespace
|
||||
const processed = this.padded_trim(content);
|
||||
// Skip if empty after processing
|
||||
if (!processed) {
|
||||
return '';
|
||||
}
|
||||
// Plain text - escape for JavaScript string
|
||||
const escaped = this.escape_string(content);
|
||||
// Generate output code
|
||||
const escaped = this.escape_string(processed);
|
||||
const output = `_output.push(${escaped});`;
|
||||
// Optimization: skip consecutive identical space pushes (but never skip newlines)
|
||||
if (output === '_output.push(" ");' && this.lastOutput === '_output.push(" ");') {
|
||||
return ''; // Skip duplicate space push
|
||||
}
|
||||
// Track this output for next comparison
|
||||
this.lastOutput = output;
|
||||
// Track the emitted position with source mapping
|
||||
if (this.enablePositionTracking) {
|
||||
this.emit(output, node);
|
||||
@@ -637,9 +718,14 @@ export class CodeGenerator {
|
||||
return output;
|
||||
}
|
||||
generate_expression(node) {
|
||||
this.lastOutput = ''; // Reset for non-text output
|
||||
let output;
|
||||
// Special handling for content() calls
|
||||
const trimmedCode = node.code.trim();
|
||||
// Strip trailing semicolon if present (optional in <%= %> blocks)
|
||||
let trimmedCode = node.code.trim();
|
||||
if (trimmedCode.endsWith(';')) {
|
||||
trimmedCode = trimmedCode.slice(0, -1).trim();
|
||||
}
|
||||
if (trimmedCode === 'content()') {
|
||||
// Default slot/content - check _inner_html first
|
||||
output = `(() => { if (this.args._inner_html) { _output.push(this.args._inner_html); } else if (typeof content === 'function') { const [contentInstructions] = content.call(this); _output.push(['_content', contentInstructions]); } })();`;
|
||||
@@ -651,17 +737,8 @@ export class CodeGenerator {
|
||||
}
|
||||
else if (trimmedCode.match(/^content\s*\(\s*['"]([^'"]+)['"]\s*(?:,\s*(.+?))?\s*\)$/)) {
|
||||
// Named slot: content('header') or content('header', data) (function call with string parameter and optional data)
|
||||
const match = trimmedCode.match(/^content\s*\(\s*['"]([^'"]+)['"]\s*(?:,\s*(.+?))?\s*\)$/);
|
||||
const slotName = match[1];
|
||||
const dataParam = match[2];
|
||||
if (dataParam) {
|
||||
// With data parameter: content('row', record) -> pass data to slot function
|
||||
output = `(() => { if (typeof content === 'object' && typeof content.${slotName} === 'function') { const [contentInstructions] = content.${slotName}.call(this, ${dataParam}); _output.push(['_content', contentInstructions]); } })();`;
|
||||
}
|
||||
else {
|
||||
// Without data parameter: content('header') -> pass undefined
|
||||
output = `(() => { if (typeof content === 'object' && typeof content.${slotName} === 'function') { const [contentInstructions] = content.${slotName}.call(this); _output.push(['_content', contentInstructions]); } })();`;
|
||||
}
|
||||
// Use the standard result pattern for proper handling
|
||||
output = `(() => { const result = ${trimmedCode};; if (Array.isArray(result)) { if (result.length === 2 && Array.isArray(result[0])) { _output.push(...result[0]); } else { _output.push(...result); } } else { _output.push(jqhtml.escape_html(result)); } })();`;
|
||||
}
|
||||
else if (node.escaped) {
|
||||
// Single-line expression handler for escaped output
|
||||
@@ -819,15 +896,48 @@ export class CodeGenerator {
|
||||
}
|
||||
generate_tag_open(node) {
|
||||
// Generate just the opening tag
|
||||
const attrs = this.generate_attributes_object(node.attributes);
|
||||
const attrs = this.generate_attributes_with_conditionals(node.attributes, node.conditionalAttributes);
|
||||
return `_output.push({tag: ["${node.name}", ${attrs}, ${node.selfClosing || false}]});`;
|
||||
}
|
||||
generate_html_tag(node) {
|
||||
// Generate opening tag, children, and closing tag on same line
|
||||
// This keeps related content together
|
||||
this.lastOutput = ''; // Reset for non-text output
|
||||
// Check if this tag needs raw content preservation
|
||||
if (node.preserveWhitespace && !node.selfClosing && node.children.length > 0) {
|
||||
// Validate: only TEXT, EXPRESSION, and CODE_BLOCK children allowed in raw content tags
|
||||
// HTML tags and components are not allowed (they would break whitespace preservation)
|
||||
for (const child of node.children) {
|
||||
if (child.type !== NodeType.TEXT &&
|
||||
child.type !== NodeType.EXPRESSION &&
|
||||
child.type !== NodeType.CODE_BLOCK) {
|
||||
const error = new JQHTMLParseError(`Invalid content in <${node.name}> tag`, node.line, node.column || 0, this.sourceContent, this.sourceFile);
|
||||
error.suggestion =
|
||||
`\n\nAll content within <textarea> and <pre> tags must be plain text or expressions.\n` +
|
||||
`HTML tags and components are not allowed.\n\n` +
|
||||
`Allowed:\n` +
|
||||
` <textarea><%= this.data.value %></textarea> ← expressions OK\n` +
|
||||
` <textarea>plain text</textarea> ← plain text OK\n\n` +
|
||||
`Not allowed:\n` +
|
||||
` <textarea><div>content</div></textarea> ← HTML tags not OK\n` +
|
||||
` <textarea><MyComponent /></textarea> ← components not OK\n\n` +
|
||||
`This ensures proper whitespace preservation.`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// Generate rawtag instruction with raw content
|
||||
const attrs_obj = this.generate_attributes_with_conditionals(node.attributes, node.conditionalAttributes);
|
||||
// Collect raw content from children (all validated as TEXT)
|
||||
let rawContent = '';
|
||||
for (const child of node.children) {
|
||||
rawContent += child.content;
|
||||
}
|
||||
// Escape the raw content for JavaScript string
|
||||
const escapedContent = this.escape_string(rawContent);
|
||||
return `_output.push({rawtag: ["${node.name}", ${attrs_obj}, ${escapedContent}]});`;
|
||||
}
|
||||
// Normal tag generation
|
||||
const parts = [];
|
||||
// Generate opening tag instruction
|
||||
const attrs_obj = this.generate_attributes_object(node.attributes);
|
||||
const attrs_obj = this.generate_attributes_with_conditionals(node.attributes, node.conditionalAttributes);
|
||||
parts.push(`_output.push({tag: ["${node.name}", ${attrs_obj}, ${node.selfClosing}]});`);
|
||||
if (!node.selfClosing) {
|
||||
// Generate children inline
|
||||
@@ -849,8 +959,9 @@ export class CodeGenerator {
|
||||
return parts.join(' ');
|
||||
}
|
||||
generate_component_invocation(node) {
|
||||
this.lastOutput = ''; // Reset for non-text output
|
||||
const instructions = [];
|
||||
const attrs_obj = this.generate_attributes_object(node.attributes);
|
||||
const attrs_obj = this.generate_attributes_with_conditionals(node.attributes, node.conditionalAttributes);
|
||||
if (node.selfClosing || node.children.length === 0) {
|
||||
// Simple component without children
|
||||
const componentCall = `_output.push({comp: ["${node.name}", ${attrs_obj}]});`;
|
||||
@@ -902,6 +1013,23 @@ export class CodeGenerator {
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
// Generate attribute object including conditional attributes
|
||||
generate_attributes_with_conditionals(attrs, conditionalAttrs) {
|
||||
// If no conditional attributes, use simple object
|
||||
if (!conditionalAttrs || conditionalAttrs.length === 0) {
|
||||
return this.generate_attributes_object(attrs);
|
||||
}
|
||||
// We have conditional attributes - need to merge them at runtime
|
||||
const baseAttrs = this.generate_attributes_object(attrs);
|
||||
// Generate code that conditionally adds attributes
|
||||
let result = baseAttrs;
|
||||
for (const condAttr of conditionalAttrs) {
|
||||
const condAttrsObj = this.generate_attributes_object(condAttr.attributes);
|
||||
// Use Object.assign to merge conditional attributes
|
||||
result = `Object.assign({}, ${result}, (${condAttr.condition}) ? ${condAttrsObj} : {})`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
generate_attributes_object(attrs) {
|
||||
if (Object.keys(attrs).length === 0) {
|
||||
return '{}';
|
||||
@@ -909,11 +1037,14 @@ export class CodeGenerator {
|
||||
const entries = Object.entries(attrs).flatMap(([key, value]) => {
|
||||
// Convert 'tag' to '_tag' for component invocations
|
||||
const attrKey = key === 'tag' ? '_tag' : key;
|
||||
// Special handling for id attribute - append _cid for scoping
|
||||
if (key === 'id') {
|
||||
// Special handling for data-id attribute (from $id) - create scoped id
|
||||
// NOTE: Parser converts $id="foo" → data-id="foo" so we can distinguish from regular id
|
||||
// This generates: id="foo:PARENT_CID" data-id="foo"
|
||||
// The :PARENT_CID scoping happens at runtime in instruction-processor.ts
|
||||
if (key === 'data-id') {
|
||||
const id_entries = [];
|
||||
if (value && typeof value === 'object' && value.interpolated) {
|
||||
// Interpolated ID like id="user<%= index %>"
|
||||
// Interpolated $id like $id="user<%= index %>"
|
||||
const parts = value.parts.map((part) => {
|
||||
if (part.type === 'text') {
|
||||
return this.escape_string(part.value);
|
||||
@@ -927,19 +1058,29 @@ export class CodeGenerator {
|
||||
id_entries.push(`"data-id": ${base_id}`);
|
||||
}
|
||||
else if (value && typeof value === 'object' && value.quoted) {
|
||||
// Quoted ID like $id="static"
|
||||
// Quoted $id like $id="static"
|
||||
const base_id = this.escape_string(value.value);
|
||||
id_entries.push(`"id": ${base_id} + ":" + this._cid`);
|
||||
id_entries.push(`"data-id": ${base_id}`);
|
||||
}
|
||||
else {
|
||||
// Simple ID like id="username" or expression like $id=someVar
|
||||
// Simple $id like $id="username" or expression like $id=someVar
|
||||
const base_id = this.escape_string(String(value));
|
||||
id_entries.push(`"id": ${base_id} + ":" + this._cid`);
|
||||
id_entries.push(`"data-id": ${base_id}`);
|
||||
}
|
||||
return id_entries;
|
||||
}
|
||||
// Regular id attribute - pass through unchanged
|
||||
// id="foo" remains id="foo" (no scoping)
|
||||
if (key === 'id') {
|
||||
if (value && typeof value === 'object' && value.quoted) {
|
||||
return `"id": ${this.escape_string(value.value)}`;
|
||||
}
|
||||
else {
|
||||
return `"id": ${this.escape_string(String(value))}`;
|
||||
}
|
||||
}
|
||||
// Check if this is an interpolated attribute value
|
||||
if (value && typeof value === 'object' && value.interpolated) {
|
||||
// Build concatenation expression
|
||||
@@ -948,8 +1089,9 @@ export class CodeGenerator {
|
||||
return this.escape_string(part.value);
|
||||
}
|
||||
else {
|
||||
// Expression - no escaping in attributes
|
||||
return part.value;
|
||||
// Expression - wrap in parentheses to preserve operator precedence
|
||||
// This ensures "a" + (x ? 'b' : 'c') instead of "a" + x ? 'b' : 'c'
|
||||
return `(${part.value})`;
|
||||
}
|
||||
});
|
||||
return `"${attrKey}": ${parts.join(' + ')}`;
|
||||
@@ -1201,7 +1343,7 @@ export class CodeGenerator {
|
||||
for (const [name, component] of this.components) {
|
||||
code += `// Component: ${name}\n`;
|
||||
code += `jqhtml_components.set('${name}', {\n`;
|
||||
code += ` _jqhtml_version: '2.2.142',\n`; // Version will be replaced during build
|
||||
code += ` _jqhtml_version: '2.2.171',\n`; // Version will be replaced during build
|
||||
code += ` name: '${name}',\n`;
|
||||
code += ` tag: '${component.tagName}',\n`;
|
||||
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;
|
||||
|
||||
Reference in New Issue
Block a user