diff --git a/packages/@glimmer/compiler/lib/template-compiler.ts b/packages/@glimmer/compiler/lib/template-compiler.ts index c9b383e583..46149f3a16 100644 --- a/packages/@glimmer/compiler/lib/template-compiler.ts +++ b/packages/@glimmer/compiler/lib/template-compiler.ts @@ -129,8 +129,15 @@ export default class TemplateCompiler implements Processor { // TODO: Assert no attributes let typeAttr: Option = null; let attrs = action.attributes; + for (let i = 0; i < attrs.length; i++) { - if (attrs[i].name === 'type') { + // Unlike most attributes, the `type` attribute can change how + // subsequent attributes are interpreted by the browser. To address + // this, in simple cases, we special case the `type` attribute to be set + // last. For elements with splattributes, where attribute order affects + // precedence, this re-ordering happens at runtime instead. + // See https://github.com/glimmerjs/glimmer-vm/pull/726 + if (simple && attrs[i].name === 'type') { typeAttr = attrs[i]; continue; } diff --git a/packages/@glimmer/integration-tests/lib/suites/components.ts b/packages/@glimmer/integration-tests/lib/suites/components.ts index afe8bb805b..8028d72ac1 100644 --- a/packages/@glimmer/integration-tests/lib/suites/components.ts +++ b/packages/@glimmer/integration-tests/lib/suites/components.ts @@ -765,6 +765,16 @@ export class BasicComponents extends RenderTest { this.assertHTML('
'); } + @test({ kind: 'glimmer' }) + 'angle bracket invocation can allow invocation side to override the type attribute with ...attributes'() { + this.registerComponent('Glimmer', 'Qux', '
'); + this.registerComponent('Glimmer', 'Bar', ''); + this.registerComponent('Glimmer', 'Foo', ''); + + this.render(''); + this.assertHTML('
'); + } + @test({ kind: 'glimmer' }) 'angle bracket invocation can override invocation side attributes with ...attributes'() { this.registerComponent('Glimmer', 'Qux', '
'); @@ -775,6 +785,16 @@ export class BasicComponents extends RenderTest { this.assertHTML('
'); } + @test({ kind: 'glimmer' }) + 'angle bracket invocation can override invocation side type attribute with ...attributes'() { + this.registerComponent('Glimmer', 'Qux', '
'); + this.registerComponent('Glimmer', 'Bar', ''); + this.registerComponent('Glimmer', 'Foo', ''); + + this.render(''); + this.assertHTML('
'); + } + @test({ kind: 'glimmer' }) 'angle bracket invocation can forward classes before ...attributes to a nested component'() { this.registerComponent('Glimmer', 'Qux', '
'); diff --git a/packages/@glimmer/integration-tests/test/input-range-test.ts b/packages/@glimmer/integration-tests/test/input-range-test.ts index 644080ec43..46bfc135a0 100644 --- a/packages/@glimmer/integration-tests/test/input-range-test.ts +++ b/packages/@glimmer/integration-tests/test/input-range-test.ts @@ -76,26 +76,8 @@ class EmberInputRangeComponent extends EmberishCurlyComponent { type = 'range'; } -abstract class EmberComponentRangeTests extends RangeTests { - abstract component(): EmberishCurlyComponentFactory; - - renderRange(value: number): void { - this.registerComponent('Curly', 'range-input', '', this.component()); - this.render(`{{range-input max=max min=min value=value}}`, { - max: this.max, - min: this.min, - value, - }); - } - - assertRangeValue(value: number): void { - let attr = (this.element.firstChild as any)['value']; - this.assert.equal(attr, value.toString()); - } -} - jitSuite( - class extends EmberComponentRangeTests { + class EmberComponentRangeTests extends RangeTests { static suiteName = `Components - [emberjs/ember.js#15675] - type value min max`; component(): EmberishCurlyComponentFactory { @@ -103,26 +85,74 @@ jitSuite( attributeBindings = ['type', 'value', 'min', 'max']; } as any; } + + renderRange(value: number): void { + this.registerComponent('Curly', 'range-input', '', this.component()); + this.render(`{{range-input max=max min=min value=value}}`, { + max: this.max, + min: this.min, + value, + }); + } + + assertRangeValue(value: number): void { + let attr = (this.element.firstChild as any)['value']; + this.assert.equal(attr, value.toString()); + } } ); -class BasicComponentImplicitAttributesRangeTest extends RangeTests { - attrs!: string; +jitSuite( + class BasicComponentImplicitAttributesRangeTest extends RangeTests { + static suiteName = `integration - GlimmerComponent - [emberjs/ember.js#15675] ...attributes `; + attrs = 'type="range" value="%x" min="-5" max="50"'; - renderRange(value: number): void { - this.registerComponent('Glimmer', 'RangeInput', ''); - this.render(``); - } + renderRange(value: number): void { + this.registerComponent('Glimmer', 'RangeInput', ''); + this.render(``); + } - assertRangeValue(value: number): void { - let attr = this.readDOMAttr('value'); - this.assert.equal(attr, value.toString()); + assertRangeValue(value: number): void { + let attr = this.readDOMAttr('value'); + this.assert.equal(attr, value.toString()); + } } -} +); jitSuite( - class extends BasicComponentImplicitAttributesRangeTest { - static suiteName = `integration - GlimmerComponent - [emberjs/ember.js#15675] ...attributes `; + class BasicComponentSplattributesLastRangeTest extends RangeTests { + static suiteName = `integration - GlimmerComponent - [emberjs/ember.js#15675] ...attributes last `; attrs = 'type="range" value="%x" min="-5" max="50"'; + + renderRange(value: number): void { + this.registerComponent('Glimmer', 'RangeInput', ''); + this.render(``); + } + + assertRangeValue(value: number): void { + let attr = this.readDOMAttr('value'); + this.assert.equal(attr, value.toString()); + } + } +); + +jitSuite( + class BasicComponentSplattributesFirstRangeTest extends RangeTests { + static suiteName = `integration - GlimmerComponent - [emberjs/ember.js#15675] ...attributes first `; + attrs = 'type="text" min="-5" max="50"'; + + renderRange(value: number): void { + this.registerComponent( + 'Glimmer', + 'RangeInput', + `` + ); + this.render(``); + } + + assertRangeValue(value: number): void { + let attr = this.readDOMAttr('value'); + this.assert.equal(attr, value.toString()); + } } );