boxwood 2.18.0 → 2.18.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boxwood",
3
- "version": "2.18.0",
3
+ "version": "2.18.1",
4
4
  "description": "Compile HTML templates into JS",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -175,6 +175,17 @@ literals:
175
175
  <Hero sources="{[cover ?? 'default.jpg', banner]}" />
176
176
  ```
177
177
 
178
+ ## Attribute Interpolation
179
+
180
+ An attribute whose entire value is a single `{expression}` resolves to the
181
+ raw value (array, object, number) - that is what makes
182
+ `images="{images.slice(0, 2)}"` work. Expressions can also be embedded in a
183
+ longer attribute string, which always produces a string:
184
+
185
+ ```markdown
186
+ <Card href="/products/{id}" alt="Photo of {name ?? 'someone'}" />
187
+ ```
188
+
178
189
  ## Conditional Rendering
179
190
 
180
191
  Use `{#if}` blocks to conditionally render content based on data:
@@ -17,7 +17,6 @@ const CONDITION_REGEXP = /^(.+?)\s*(>=|<=|>|<|==|!=)\s*(.+)$/
17
17
  const INLINE_CODE_REGEXP = /(`+)([^`]+)\1/g
18
18
  const BRACE_REGEXP = /\{([^{}]*)\}/g
19
19
  const COMPONENT_TAG_REGEXP = /^<\/?([A-Z][A-Za-z0-9-]*)/
20
- const ATTRIBUTE_EXPRESSION_REGEXP = /=\s*(?:"\{([^}"]*)\}"|'\{([^}']*)\}'|\{([^}]*)\})/g
21
20
 
22
21
  /**
23
22
  * Validate a Prose template and report problems that the renderer
@@ -253,19 +252,8 @@ function validate(text, options = {}) {
253
252
  )
254
253
  }
255
254
  }
256
- if (componentMatch) {
257
- // Validate attribute expressions: attr="{expr}" or attr={expr}
258
- let attrMatch
259
- while ((attrMatch = ATTRIBUTE_EXPRESSION_REGEXP.exec(trimmed)) !== null) {
260
- const expression = attrMatch[1] ?? attrMatch[2] ?? attrMatch[3]
261
- if (expression && expression.trim()) {
262
- validateExpression(expression, lineNumber)
263
- }
264
- }
265
- continue
266
- }
267
-
268
- // Braced tags and expressions
255
+ // Braced tags and expressions - on component lines this also covers
256
+ // attribute expressions, both whole-value ({expr}) and partial (/p/{id})
269
257
  let braceMatch
270
258
  while ((braceMatch = BRACE_REGEXP.exec(line)) !== null) {
271
259
  // Escaped braces are literal
@@ -1,4 +1,7 @@
1
- const { resolveExpression } = require("./replaceVariables")
1
+ const {
2
+ resolveExpression,
3
+ replaceVariables,
4
+ } = require("./replaceVariables")
2
5
 
3
6
  /**
4
7
  * Parse a custom component tag from markdown
@@ -160,18 +163,24 @@ function parseAttributes(attributesStr) {
160
163
  }
161
164
  }
162
165
 
163
- // Check if the entire quoted value is a variable reference: "{variable}"
164
- if (value.startsWith("{") && value.endsWith("}")) {
165
- const variableName = value.substring(1, value.length - 1).trim()
166
- if (variableName) {
167
- attributes[name] = { __variable: variableName }
168
- } else {
169
- attributes[name] = value
170
- }
166
+ // Whole-value variable reference: "{variable}" - resolves to the raw
167
+ // value (array, object, number), not a string
168
+ const inner = value.substring(1, value.length - 1)
169
+ if (
170
+ value.startsWith("{") &&
171
+ value.endsWith("}") &&
172
+ inner.trim() &&
173
+ !inner.includes("{") &&
174
+ !inner.includes("}")
175
+ ) {
176
+ attributes[name] = { __variable: inner.trim() }
177
+ } else if (/\{[^{}]+\}/.test(value)) {
178
+ // Partial interpolation: "/products/{id}" or "Photo of {name}"
179
+ attributes[name] = { __interpolate: value }
171
180
  } else {
172
181
  attributes[name] = value
173
182
  }
174
-
183
+
175
184
  if (i < str.length) i++ // Skip closing quote
176
185
  } else if (str[i] === "{") {
177
186
  // Variable reference
@@ -216,6 +225,9 @@ function resolveAttributes(attributes, data) {
216
225
  const variablePath = value.__variable
217
226
  const resolvedValue = resolveExpression(data, variablePath)
218
227
  resolved[key] = resolvedValue
228
+ } else if (value && typeof value === "object" && value.__interpolate) {
229
+ // Partial interpolation always produces a string
230
+ resolved[key] = replaceVariables(value.__interpolate, data)
219
231
  } else {
220
232
  resolved[key] = value
221
233
  }