pulse-js-framework 1.7.24 → 1.7.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,10 +25,14 @@ export function transformExpression(transformer, node) {
25
25
 
26
26
  switch (node.type) {
27
27
  case NodeType.Identifier:
28
+ // Props take precedence over state (props are destructured in render scope)
29
+ if (transformer.propVars.has(node.name)) {
30
+ return node.name;
31
+ }
28
32
  if (transformer.stateVars.has(node.name)) {
29
33
  return `${node.name}.get()`;
30
34
  }
31
- // Props are accessed directly (already destructured)
35
+ // Other identifiers (actions, imports, etc.) accessed directly
32
36
  return node.name;
33
37
 
34
38
  case NodeType.Literal:
@@ -153,8 +157,13 @@ export function transformExpression(transformer, node) {
153
157
  */
154
158
  export function transformExpressionString(transformer, exprStr) {
155
159
  // Simple transformation: wrap state vars with .get()
160
+ // Props take precedence - don't wrap props with .get()
156
161
  let result = exprStr;
157
162
  for (const stateVar of transformer.stateVars) {
163
+ // Skip if this var name is also a prop (props shadow state in render scope)
164
+ if (transformer.propVars.has(stateVar)) {
165
+ continue;
166
+ }
158
167
  result = result.replace(
159
168
  new RegExp(`\\b${stateVar}\\b`, 'g'),
160
169
  `${stateVar}.get()`
@@ -298,32 +298,127 @@ export function transformFocusTrapDirective(transformer, node, indent) {
298
298
  return `{ ${optionsCode} }`;
299
299
  }
300
300
 
301
+ /**
302
+ * Parse a balanced expression starting from an opening brace
303
+ * Handles nested braces and string literals correctly
304
+ * @param {string} str - The string to parse
305
+ * @param {number} start - Index of the opening brace
306
+ * @returns {Object} { expr: string, end: number } or null if invalid
307
+ */
308
+ function parseBalancedExpression(str, start) {
309
+ if (str[start] !== '{') return null;
310
+
311
+ let depth = 0;
312
+ let inString = false;
313
+ let stringChar = '';
314
+ let i = start;
315
+
316
+ while (i < str.length) {
317
+ const char = str[i];
318
+ const prevChar = i > 0 ? str[i - 1] : '';
319
+
320
+ // Handle string literals
321
+ if (!inString && (char === '"' || char === "'" || char === '`')) {
322
+ inString = true;
323
+ stringChar = char;
324
+ } else if (inString && char === stringChar && prevChar !== '\\') {
325
+ inString = false;
326
+ stringChar = '';
327
+ }
328
+
329
+ // Count braces only outside strings
330
+ if (!inString) {
331
+ if (char === '{') {
332
+ depth++;
333
+ } else if (char === '}') {
334
+ depth--;
335
+ if (depth === 0) {
336
+ // Found the matching closing brace
337
+ return {
338
+ expr: str.slice(start + 1, i),
339
+ end: i
340
+ };
341
+ }
342
+ }
343
+ }
344
+
345
+ i++;
346
+ }
347
+
348
+ return null; // Unbalanced braces
349
+ }
350
+
301
351
  /**
302
352
  * Extract dynamic attributes from a selector
303
353
  * Returns { cleanSelector, dynamicAttrs } where dynamicAttrs is an array of { name, expr }
354
+ * Handles complex expressions including ternaries, nested braces, and string literals
304
355
  * @param {string} selector - CSS selector with potential dynamic attributes
305
356
  * @returns {Object} { cleanSelector, dynamicAttrs }
306
357
  */
307
358
  function extractDynamicAttributes(selector) {
308
359
  const dynamicAttrs = [];
309
- // Match attributes with {expression} values: [name={expr}] or [name="{expr}"]
310
- const attrPattern = /\[([a-zA-Z][a-zA-Z0-9-]*)\s*=\s*\{([^}]+)\}\]/g;
311
- const attrPatternQuoted = /\[([a-zA-Z][a-zA-Z0-9-]*)\s*=\s*"\{([^}]+)\}"\]/g;
360
+ let cleanSelector = '';
361
+ let i = 0;
362
+
363
+ while (i < selector.length) {
364
+ // Look for attribute start: [
365
+ if (selector[i] === '[') {
366
+ i++; // Skip [
367
+
368
+ // Parse attribute name
369
+ let attrName = '';
370
+ while (i < selector.length && /[a-zA-Z0-9-]/.test(selector[i])) {
371
+ attrName += selector[i];
372
+ i++;
373
+ }
312
374
 
313
- let cleanSelector = selector;
375
+ // Skip whitespace
376
+ while (i < selector.length && /\s/.test(selector[i])) i++;
314
377
 
315
- // Extract unquoted dynamic attributes: [value={expr}]
316
- let match;
317
- while ((match = attrPattern.exec(selector)) !== null) {
318
- dynamicAttrs.push({ name: match[1], expr: match[2] });
319
- }
320
- cleanSelector = cleanSelector.replace(attrPattern, '');
378
+ // Check for =
379
+ if (i < selector.length && selector[i] === '=') {
380
+ i++; // Skip =
381
+
382
+ // Skip whitespace
383
+ while (i < selector.length && /\s/.test(selector[i])) i++;
321
384
 
322
- // Extract quoted dynamic attributes: [value="{expr}"]
323
- while ((match = attrPatternQuoted.exec(selector)) !== null) {
324
- dynamicAttrs.push({ name: match[1], expr: match[2] });
385
+ // Check for optional quote
386
+ const hasQuote = selector[i] === '"';
387
+ if (hasQuote) i++;
388
+
389
+ // Check for dynamic expression {
390
+ if (selector[i] === '{') {
391
+ const result = parseBalancedExpression(selector, i);
392
+ if (result) {
393
+ dynamicAttrs.push({ name: attrName, expr: result.expr });
394
+ i = result.end + 1; // Skip past closing }
395
+
396
+ // Skip optional closing quote
397
+ if (hasQuote && selector[i] === '"') i++;
398
+
399
+ // Skip closing ]
400
+ if (selector[i] === ']') i++;
401
+
402
+ // Don't add this attribute to cleanSelector
403
+ continue;
404
+ }
405
+ }
406
+ }
407
+
408
+ // Not a dynamic attribute, copy everything from [ to ]
409
+ let bracketDepth = 1;
410
+ cleanSelector += '[';
411
+ while (i < selector.length && bracketDepth > 0) {
412
+ if (selector[i] === '[') bracketDepth++;
413
+ else if (selector[i] === ']') bracketDepth--;
414
+ cleanSelector += selector[i];
415
+ i++;
416
+ }
417
+ } else {
418
+ cleanSelector += selector[i];
419
+ i++;
420
+ }
325
421
  }
326
- cleanSelector = cleanSelector.replace(attrPatternQuoted, '');
327
422
 
328
423
  return { cleanSelector, dynamicAttrs };
329
424
  }
@@ -577,6 +672,18 @@ export function transformComponentCall(transformer, node, indent) {
577
672
  * @param {number} indent - Indentation level
578
673
  * @returns {string} JavaScript code
579
674
  */
675
+ /**
676
+ * Escape a string for use in a template literal
677
+ * @param {string} str - String to escape
678
+ * @returns {string} Escaped string
679
+ */
680
+ function escapeTemplateString(str) {
681
+ return str
682
+ .replace(/\\/g, '\\\\') // Escape backslashes first
683
+ .replace(/`/g, '\\`') // Escape backticks
684
+ .replace(/\$/g, '\\$'); // Escape dollar signs to prevent ${} interpretation
685
+ }
686
+
580
687
  export function transformTextNode(transformer, node, indent) {
581
688
  const pad = ' '.repeat(indent);
582
689
  const parts = node.parts;
@@ -589,7 +696,8 @@ export function transformTextNode(transformer, node, indent) {
589
696
  // Has interpolations - use text() with a function
590
697
  const textParts = parts.map(part => {
591
698
  if (typeof part === 'string') {
592
- return JSON.stringify(part);
699
+ // Escape for template literal (not JSON.stringify which adds quotes)
700
+ return escapeTemplateString(part);
593
701
  }
594
702
  // Interpolation
595
703
  const expr = transformExpressionString(transformer, part.expression);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-js-framework",
3
- "version": "1.7.24",
3
+ "version": "1.7.25",
4
4
  "description": "A declarative DOM framework with CSS selector-based structure and reactive pulsations",
5
5
  "type": "module",
6
6
  "main": "index.js",