diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/interpolations/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/interpolations/TEST_CASES.json index fb8ac026ee5ed5..33ff1c45232d50 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/interpolations/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/interpolations/TEST_CASES.json @@ -3,75 +3,54 @@ "cases": [ { "description": "should generate the proper update instructions for interpolated classes", - "inputFiles": [ - "class_interpolations.ts" - ], + "inputFiles": ["class_interpolations.ts"], "expectations": [ { "failureMessage": "Incorrect handling of interpolated classes", - "files": [ - "class_interpolations.js" - ] + "files": ["class_interpolations.js"] } ], "skipForTemplatePipeline": true }, { "description": "should generate the proper update instructions for interpolated style properties", - "inputFiles": [ - "style_properties.ts" - ], + "inputFiles": ["style_properties.ts"], "expectations": [ { "failureMessage": "Incorrect handling of interpolated style properties", - "files": [ - "style_properties.js" - ] + "files": ["style_properties.js"] } ], "skipForTemplatePipeline": true }, { "description": "should generate update instructions for interpolated style properties with a suffix", - "inputFiles": [ - "style_binding_suffixed.ts" - ], + "inputFiles": ["style_binding_suffixed.ts"], "expectations": [ { "failureMessage": "Incorrect handling of interpolated style properties", - "files": [ - "style_binding_suffixed.js" - ] + "files": ["style_binding_suffixed.js"] } - ], - "skipForTemplatePipeline": true + ] }, { "description": "should generate update instructions for interpolated style properties with a sanitizer", - "inputFiles": [ - "style_binding_sanitizer.ts" - ], + "inputFiles": ["style_binding_sanitizer.ts"], "expectations": [ { "failureMessage": "Incorrect handling of interpolated style properties", - "files": [ - "style_binding_sanitizer.js" - ] + "files": ["style_binding_sanitizer.js"] } ], "skipForTemplatePipeline": true }, { "description": "should generate update instructions for interpolated style properties with !important", - "inputFiles": [ - "style_binding_important.ts" - ], + "inputFiles": ["style_binding_important.ts"], "expectations": [ { "failureMessage": "Incorrect handling of interpolated style properties", - "files": [ - "style_binding_important.js" - ] + "files": ["style_binding_important.js"] } ], "skipForTemplatePipeline": true diff --git a/packages/compiler/src/template/pipeline/ir/src/enums.ts b/packages/compiler/src/template/pipeline/ir/src/enums.ts index 3da983a209978e..f0ef314463da4a 100644 --- a/packages/compiler/src/template/pipeline/ir/src/enums.ts +++ b/packages/compiler/src/template/pipeline/ir/src/enums.ts @@ -98,6 +98,11 @@ export enum OpKind { */ InterpolateProperty, + /** + * An operation to interpolate text into a style property binding. + */ + InterpolateStyleProp, + /** * An operation to advance the runtime's implicit slot context during the update phase of a view. */ diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index 4d683ed5f75488..1f659402ed0b6a 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -660,6 +660,8 @@ export function transformExpressionsInOp( op.expression = transformExpressionsInExpression(op.expression, transform, flags); break; case OpKind.InterpolateProperty: + case OpKind.InterpolateStyleProp: + case OpKind.InterpolateText: for (let i = 0; i < op.expressions.length; i++) { op.expressions[i] = transformExpressionsInExpression(op.expressions[i], transform, flags); } @@ -675,11 +677,6 @@ export function transformExpressionsInOp( case OpKind.Variable: op.initializer = transformExpressionsInExpression(op.initializer, transform, flags); break; - case OpKind.InterpolateText: - for (let i = 0; i < op.expressions.length; i++) { - op.expressions[i] = transformExpressionsInExpression(op.expressions[i], transform, flags); - } - break; case OpKind.Listener: for (const innerOp of op.handlerOps) { transformExpressionsInOp(innerOp, transform, flags | VisitorContextFlag.InChildOperation); diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts index 75d5d3e680f7e4..9b4599856af9de 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts @@ -18,7 +18,8 @@ import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared'; * An operation usable on the update side of the IR. */ export type UpdateOp = ListEndOp|StatementOp|PropertyOp|StylePropOp|StyleMapOp| - InterpolatePropertyOp|AttributeOp|InterpolateTextOp|AdvanceOp|VariableOp; + InterpolatePropertyOp|InterpolateStylePropOp|AttributeOp|InterpolateTextOp|AdvanceOp| + VariableOp; /** * A logical operation to perform string interpolation on a text node. @@ -133,7 +134,7 @@ export interface StylePropOp extends Op, ConsumesVarsTrait, DependsOnS expression: o.Expression; /** - * The unit of the expression value. + * The unit of the bound value. */ unit: string|null; } @@ -281,7 +282,61 @@ export function createInterpolatePropertyOp( }; } +/** + * A logical operation representing binding an interpolation to a style property in the update IR. + */ +export interface InterpolateStylePropOp extends Op, ConsumesVarsTrait, + DependsOnSlotContextOpTrait { + kind: OpKind.InterpolateStyleProp; + + /** + * Reference to the element on which the property is bound. + */ + target: XrefId; + + /** + * Name of the bound property. + */ + name: string; + + /** + * All of the literal strings in the property interpolation, in order. + * + * Conceptually interwoven around the `expressions`. + */ + strings: string[]; + + /** + * All of the dynamic expressions in the property interpolation, in order. + * + * Conceptually interwoven in between the `strings`. + */ + expressions: o.Expression[]; + /** + * The unit of the bound value. + */ + unit: string|null; +} + +/** + * Create a `InterpolateStyleProp`. + */ +export function createInterpolateStylePropOp( + xref: XrefId, name: string, strings: string[], expressions: o.Expression[], + unit: string|null): InterpolateStylePropOp { + return { + kind: OpKind.InterpolateStyleProp, + target: xref, + name, + strings, + expressions, + unit, + ...TRAIT_DEPENDS_ON_SLOT_CONTEXT, + ...TRAIT_CONSUMES_VARS, + ...NEW_OP, + }; +} /** * Logical operation to advance the runtime's internal slot pointer in the update IR. diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index 54f1496c99833a..e08d3add9b2155 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -266,6 +266,14 @@ function ingestPropertyBinding( xref, bindingKind, name, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl)))); break; + case e.BindingType.Style: + if (bindingKind !== ir.ElementAttributeKind.Binding) { + throw Error('Unexpected style binding on ng-template'); + } + view.update.push(ir.createInterpolateStylePropOp( + xref, name, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl)), + unit)); + break; default: // TODO: implement remaining binding types. throw Error(`Interpolated property binding type not handled: ${type}`); diff --git a/packages/compiler/src/template/pipeline/src/instruction.ts b/packages/compiler/src/template/pipeline/src/instruction.ts index bffea65e09cc92..0acf61fe8cb1cd 100644 --- a/packages/compiler/src/template/pipeline/src/instruction.ts +++ b/packages/compiler/src/template/pipeline/src/instruction.ts @@ -204,6 +204,39 @@ export function textInterpolate(strings: string[], expressions: o.Expression[]): export function propertyInterpolate( name: string, strings: string[], expressions: o.Expression[]): ir.UpdateOp { + const interpolationArgs = collateInterpolationArgs(strings, expressions); + + return callVariadicInstruction(PROPERTY_INTERPOLATE_CONFIG, [o.literal(name)], interpolationArgs); +} + +export function stylePropInterpolate( + name: string, strings: string[], expressions: o.Expression[], unit: string|null): ir.UpdateOp { + const interpolationArgs = collateInterpolationArgs(strings, expressions); + const extraArgs: o.Expression[] = []; + if (unit !== null) { + extraArgs.push(o.literal(unit)); + } + + return callVariadicInstruction( + STYLE_PROP_INTERPOLATE_CONFIG, [o.literal(name)], interpolationArgs, extraArgs); +} + +export function pureFunction( + varOffset: number, fn: o.Expression, args: o.Expression[]): o.Expression { + return callVariadicInstructionExpr( + PURE_FUNCTION_CONFIG, + [ + o.literal(varOffset), + fn, + ], + args, + ); +} + +/** + * Collates the string an expression arguments for an interpolation instruction. + */ +function collateInterpolationArgs(strings: string[], expressions: o.Expression[]): o.Expression[] { if (strings.length < 1 || expressions.length !== strings.length - 1) { throw new Error( `AssertionError: expected specific shape of args for strings/expressions in interpolation`); @@ -221,19 +254,7 @@ export function propertyInterpolate( interpolationArgs.push(o.literal(strings[idx])); } - return callVariadicInstruction(PROPERTY_INTERPOLATE_CONFIG, [o.literal(name)], interpolationArgs); -} - -export function pureFunction( - varOffset: number, fn: o.Expression, args: o.Expression[]): o.Expression { - return callVariadicInstructionExpr( - PURE_FUNCTION_CONFIG, - [ - o.literal(varOffset), - fn, - ], - args, - ); + return interpolationArgs; } function call( @@ -300,6 +321,33 @@ const PROPERTY_INTERPOLATE_CONFIG: VariadicInstructionConfig = { }, }; +/** + * `InterpolationConfig` for the `stylePropInterpolate` instruction. + */ +const STYLE_PROP_INTERPOLATE_CONFIG: VariadicInstructionConfig = { + constant: [ + null!, // Interpolation with 0 variables is not supported. + Identifiers.stylePropInterpolate1, + Identifiers.stylePropInterpolate2, + Identifiers.stylePropInterpolate3, + Identifiers.stylePropInterpolate4, + Identifiers.stylePropInterpolate5, + Identifiers.stylePropInterpolate6, + Identifiers.stylePropInterpolate7, + Identifiers.stylePropInterpolate8, + ], + variable: Identifiers.stylePropInterpolateV, + mapping: n => { + if (n % 2 === 0) { + throw new Error(`Expected odd number of arguments`); + } + if (n < 3) { + throw new Error(`Expected at least 3 arguments`); + } + return (n - 1) / 2; + }, +}; + const PURE_FUNCTION_CONFIG: VariadicInstructionConfig = { constant: [ Identifiers.pureFunction0, @@ -317,23 +365,27 @@ const PURE_FUNCTION_CONFIG: VariadicInstructionConfig = { }; function callVariadicInstructionExpr( - config: VariadicInstructionConfig, baseArgs: o.Expression[], - interpolationArgs: o.Expression[]): o.Expression { + config: VariadicInstructionConfig, baseArgs: o.Expression[], interpolationArgs: o.Expression[], + extraArgs: o.Expression[] = []): o.Expression { const n = config.mapping(interpolationArgs.length); if (n < config.constant.length) { // Constant calling pattern. - return o.importExpr(config.constant[n]).callFn([...baseArgs, ...interpolationArgs]); + return o.importExpr(config.constant[n]).callFn([ + ...baseArgs, ...interpolationArgs, ...extraArgs + ]); } else if (config.variable !== null) { // Variable calling pattern. - return o.importExpr(config.variable).callFn([...baseArgs, o.literalArr(interpolationArgs)]); + return o.importExpr(config.variable).callFn([ + ...baseArgs, o.literalArr(interpolationArgs), ...extraArgs + ]); } else { throw new Error(`AssertionError: unable to call variadic function`); } } function callVariadicInstruction( - config: VariadicInstructionConfig, baseArgs: o.Expression[], - interpolationArgs: o.Expression[]): ir.UpdateOp { + config: VariadicInstructionConfig, baseArgs: o.Expression[], interpolationArgs: o.Expression[], + extraArgs: o.Expression[] = []): ir.UpdateOp { return ir.createStatementOp( - callVariadicInstructionExpr(config, baseArgs, interpolationArgs).toStmt()); + callVariadicInstructionExpr(config, baseArgs, interpolationArgs, extraArgs).toStmt()); } diff --git a/packages/compiler/src/template/pipeline/src/phases/reify.ts b/packages/compiler/src/template/pipeline/src/phases/reify.ts index 1fe16acf2c1697..bf321dbaa9fda8 100644 --- a/packages/compiler/src/template/pipeline/src/phases/reify.ts +++ b/packages/compiler/src/template/pipeline/src/phases/reify.ts @@ -130,6 +130,10 @@ function reifyUpdateOperations(_view: ViewCompilation, ops: ir.OpList