diff --git a/packages/core/__tests__/transform/template-to-typescript.test.ts b/packages/core/__tests__/transform/template-to-typescript.test.ts index 47b6a0287..4e6b8671c 100644 --- a/packages/core/__tests__/transform/template-to-typescript.test.ts +++ b/packages/core/__tests__/transform/template-to-typescript.test.ts @@ -836,6 +836,15 @@ describe('Transform: rewriteTemplate', () => { }" `); }); + + test('in a conditional expression', () => { + let template = `{{(if @onSelect (modifier on "click" @onSelect))}}`; + let specialForms = { if: 'if' } as const; + + expect(templateBody(template, { globals: ['on'], specialForms })).toMatchInlineSnapshot(` + "__glintDSL__.emitContent(__glintDSL__.resolveOrReturn((__glintDSL__.noop(__if), (__glintRef__.args.onSelect) ? (__glintDSL__.applyModifier(__glintDSL__.resolve(__glintDSL__.Globals["on"])(__glintY__.element, "click", __glintRef__.args.onSelect))) : (null)))());" + `); + }); }); describe('subexpressions', () => { diff --git a/packages/core/src/transform/template/template-to-typescript.ts b/packages/core/src/transform/template/template-to-typescript.ts index 64409e88c..6364b8916 100644 --- a/packages/core/src/transform/template/template-to-typescript.ts +++ b/packages/core/src/transform/template/template-to-typescript.ts @@ -356,19 +356,94 @@ export function templateToTypescript( () => `{{${formInfo.name}}} requires at least two parameters`, ); - mapper.text('('); - emitExpression(node.params[0]); - mapper.text(') ? ('); - emitExpression(node.params[1]); - mapper.text(') : ('); + // Check if this is a conditional modifier case + const isModifierHelper = + node.params[1].type === 'SubExpression' && + node.params[1].path.type === 'PathExpression' && + node.params[1].path.original === 'modifier'; + + if (isModifierHelper) { + // For conditional modifiers, we need to handle them specially + // to avoid type instantiation depth issues + mapper.text('('); + emitExpression(node.params[0]); + mapper.text(') ? ('); + + // For the truthy case, we emit the modifier directly + if (node.params[1].type === 'SubExpression') { + // Extract the modifier and its arguments + const modifierExpr = node.params[1]; + if ( + modifierExpr.params.length > 0 && + modifierExpr.params[0].type === 'PathExpression' + ) { + mapper.text('__glintDSL__.applyModifier(__glintDSL__.resolve('); + emitExpression(modifierExpr.params[0]); + mapper.text(')(__glintY__.element, '); + + // Skip the first param (the modifier itself) and emit the rest + const restParams = modifierExpr.params.slice(1); + for (let [index, param] of restParams.entries()) { + if (index > 0) { + mapper.text(', '); + } + emitExpression(param); + } - if (node.params[2]) { - emitExpression(node.params[2]); + // Handle named args if any + if (modifierExpr.hash.pairs.length) { + if (restParams.length) { + mapper.text(', '); + } + + mapper.text('{ '); + for (let [index, pair] of modifierExpr.hash.pairs.entries()) { + mapper.text(`${pair.key}: `); + emitExpression(pair.value); + + if (index < modifierExpr.hash.pairs.length - 1) { + mapper.text(', '); + } + } + mapper.text(' }'); + } + + mapper.text('))'); + } else { + // Fallback to normal emission if structure is unexpected + emitExpression(node.params[1]); + } + } else { + // Fallback to normal emission if structure is unexpected + emitExpression(node.params[1]); + } + + mapper.text(') : ('); + + // For the falsy case, emit null or the else branch + if (node.params[2]) { + emitExpression(node.params[2]); + } else { + mapper.text('null'); + } + + mapper.text(')'); } else { - mapper.text('undefined'); - } + // Standard if expression handling (unchanged) + mapper.text('('); + emitExpression(node.params[0]); + mapper.text(') ? ('); + emitExpression(node.params[1]); + mapper.text(') : ('); + + if (node.params[2]) { + emitExpression(node.params[2]); + } else { + mapper.text('undefined'); + } - mapper.text(')'); + mapper.text(')'); + } }); } diff --git a/test-packages/ts-ember-app/app/components/test-cases/conditionall-modifier.gts b/test-packages/ts-ember-app/app/components/test-cases/conditionall-modifier.gts new file mode 100644 index 000000000..299fcb9b4 --- /dev/null +++ b/test-packages/ts-ember-app/app/components/test-cases/conditionall-modifier.gts @@ -0,0 +1,20 @@ +import { on } from "@ember/modifier"; +import type { TOC } from "@ember/component/template-only"; + +export interface ItemSignature { + Element: HTMLButtonElement; + Args: { onSelect?: (event: Event) => void }; + Blocks: { default: [] }; +} + +// https://github.com/typed-ember/glint/issues/812 +export const Item: TOC = ;