@barefootjs/jsx 0.8.0 → 0.9.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.
@@ -121,6 +121,20 @@ export function extractSsrDefaults(metadata: IRMetadata): Record<string, SsrDefa
121
121
  // fed into the bindings map so subsequent memos can reference earlier
122
122
  // signals (Counter's `doubled = createMemo(() => count() * 2)`).
123
123
  const bindings: Record<string, EvalResult> = {}
124
+
125
+ // (#checkbox) Seed module-scope constants so a memo template-literal that
126
+ // references them resolves to a concrete string. Checkbox's `classes` memo
127
+ // interpolates `baseClasses` / `focusClasses` / `errorClasses` (pure string
128
+ // consts) and `stateClasses` (`[...].join(' ')`). Without these in scope the
129
+ // memo evaluates to `null` and the SSR `class="..."` renders empty, diverging
130
+ // from Hono. Only module-scope consts are seeded (component-scope locals can
131
+ // depend on signals/props and are evaluated lazily elsewhere).
132
+ for (const c of metadata.localConstants ?? []) {
133
+ if (!c.isModule || c.value === undefined) continue
134
+ if (c.name in bindings) continue
135
+ const v = tryStaticEval(c.value, { bindings, propsLike })
136
+ if (v !== UNRESOLVED) bindings[c.name] = v
137
+ }
124
138
  for (const sig of metadata.signals) {
125
139
  if (!sig.getter || sig.isModule) continue
126
140
  const value = tryStaticEval(sig.initialValue, { bindings, propsLike })
@@ -178,12 +192,33 @@ function evalNode(node: ts.Expression, ctx: EvalContext): EvalResult {
178
192
  if (ts.isNonNullExpression(node)) return evalNode(node.expression, ctx)
179
193
 
180
194
  // `createMemo(() => count() * 2)` stores the full arrow expression
181
- // string. For evaluation, we only care about the body — and only the
182
- // expression-bodied form (`() => expr`). Block-bodied arrows
183
- // (`() => { ... }`) would need branch tracking we don't attempt.
195
+ // string. For evaluation, we only care about the body. The
196
+ // expression-bodied form (`() => expr`) evaluates its body directly; a
197
+ // block-bodied arrow (`() => { const v = …; return \`…\` }`, the Toggle
198
+ // `classes` memo) evaluates its leading `const` declarations into a local
199
+ // binding scope and then its single `return` expression. We don't attempt
200
+ // branch tracking — any control flow before the `return` leaves it
201
+ // unresolved.
184
202
  if (ts.isArrowFunction(node)) {
185
- if (node.parameters.length === 0 && !ts.isBlock(node.body)) {
186
- return evalNode(node.body as ts.Expression, ctx)
203
+ if (node.parameters.length !== 0) return UNRESOLVED
204
+ if (!ts.isBlock(node.body)) return evalNode(node.body as ts.Expression, ctx)
205
+ const localBindings: Record<string, EvalResult> = { ...ctx.bindings }
206
+ const localCtx: EvalContext = { ...ctx, bindings: localBindings }
207
+ for (const stmt of node.body.statements) {
208
+ if (ts.isVariableStatement(stmt)) {
209
+ for (const d of stmt.declarationList.declarations) {
210
+ if (!ts.isIdentifier(d.name) || !d.initializer) continue
211
+ const v = evalNode(d.initializer, localCtx)
212
+ // Leave unresolved locals unbound; only the `return` referencing
213
+ // one would then surface UNRESOLVED.
214
+ if (v !== UNRESOLVED) localBindings[d.name.text] = v
215
+ }
216
+ } else if (ts.isReturnStatement(stmt)) {
217
+ return stmt.expression ? evalNode(stmt.expression, localCtx) : UNRESOLVED
218
+ } else {
219
+ // Any other statement (a branch, a side-effecting call) — bail.
220
+ return UNRESOLVED
221
+ }
187
222
  }
188
223
  return UNRESOLVED
189
224
  }
@@ -245,10 +280,27 @@ function evalNode(node: ts.Expression, ctx: EvalContext): EvalResult {
245
280
  return arr
246
281
  }
247
282
 
248
- if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
249
- // `props.X` / `props?.X` / `props['X']` read of a binding we know
250
- // nothing about, so resolve to `undefined`. Chained access (`a.b.c`)
251
- // collapses the same way because the base read is already undefined.
283
+ if (ts.isElementAccessExpression(node)) {
284
+ // Index into a resolved object / array with a resolved scalar key — the
285
+ // Toggle `classes` memo's `variantClasses[variant]` where `variantClasses`
286
+ // is a seeded module-const object and `variant` resolved to `'default'`.
287
+ const base = evalNode(node.expression, ctx)
288
+ if (base === undefined) return undefined // `props['X']` → undefined
289
+ if (base === UNRESOLVED || base === null || typeof base !== 'object') return UNRESOLVED
290
+ if (!node.argumentExpression) return UNRESOLVED
291
+ const key = evalNode(node.argumentExpression, ctx)
292
+ if (key === UNRESOLVED || key === undefined || key === null) return UNRESOLVED
293
+ const k = String(key as string | number)
294
+ // Missing key → JS `undefined` (the `??`/`||` defaulting flows through it).
295
+ return Object.prototype.hasOwnProperty.call(base, k)
296
+ ? (base as Record<string, unknown>)[k]
297
+ : undefined
298
+ }
299
+
300
+ if (ts.isPropertyAccessExpression(node)) {
301
+ // `props.X` / `props?.X` — read of a binding we know nothing about, so
302
+ // resolve to `undefined`. Chained access (`a.b.c`) collapses the same way
303
+ // because the base read is already undefined.
252
304
  const baseResult = evalNode(node.expression, ctx)
253
305
  if (baseResult === undefined) return undefined
254
306
  return UNRESOLVED
@@ -264,6 +316,25 @@ function evalNode(node: ts.Expression, ctx: EvalContext): EvalResult {
264
316
  ) {
265
317
  return ctx.bindings[node.expression.text]
266
318
  }
319
+ // `<array>.join(<sep?>)` — evaluate when the receiver resolves to an array
320
+ // and the separator (default `,`) is a string. Covers `stateClasses =
321
+ // [...].join(' ')` (#checkbox). Other array methods stay unresolved.
322
+ if (
323
+ ts.isPropertyAccessExpression(node.expression) &&
324
+ node.expression.name.text === 'join'
325
+ ) {
326
+ const recv = evalNode(node.expression.expression, ctx)
327
+ if (Array.isArray(recv)) {
328
+ let sep = ','
329
+ if (node.arguments.length >= 1) {
330
+ const sepVal = evalNode(node.arguments[0], ctx)
331
+ if (typeof sepVal !== 'string') return UNRESOLVED
332
+ sep = sepVal
333
+ }
334
+ return recv.map(x => (x === null || x === undefined ? '' : `${x}`)).join(sep)
335
+ }
336
+ return UNRESOLVED
337
+ }
267
338
  return UNRESOLVED
268
339
  }
269
340