Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ export function parseHostStyleProperties(job: CompilationJob): void {
op.name = 'style';
} else if (op.name.startsWith(CLASS_DOT)) {
op.bindingKind = ir.BindingKind.ClassName;
op.name = parseProperty(op.name.substring(CLASS_DOT.length)).property;
// Class names can contain dots (e.g. `px-3.5`), so there is no suffix to split off.
op.name = stripImportant(op.name.substring(CLASS_DOT.length));
} else if (op.name.startsWith(CLASS_BANG)) {
op.bindingKind = ir.BindingKind.ClassName;
Comment thread
vslipchenko marked this conversation as resolved.
op.name = parseProperty(op.name.substring(CLASS_BANG.length)).property;
op.name = stripImportant(op.name.substring(CLASS_BANG.length));
}
}
}
Expand All @@ -77,11 +78,16 @@ function hyphenate(value: string): string {
.toLowerCase();
}

function parseProperty(name: string): {property: string; suffix: string | null} {
const overrideIndex = name.indexOf('!important');
function stripImportant(name: string): string {
const overrideIndex = name.indexOf(BANG_IMPORTANT);
if (overrideIndex !== -1) {
name = overrideIndex > 0 ? name.substring(0, overrideIndex) : '';
}
return name;
}

function parseProperty(name: string): {property: string; suffix: string | null} {
name = stripImportant(name);

let suffix: string | null = null;
let property = name;
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler/src/template_parser/binding_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,8 @@ export class BindingParser {

bindingType = BindingType.Attribute;
} else if (parts[0] == CLASS_PREFIX) {
boundPropertyName = parts[1];
// Class names can contain dots (e.g. `px-3.5`), so everything after the prefix is the name.
boundPropertyName = parts.slice(1).join(PROPERTY_PARTS_SEPARATOR);
bindingType = BindingType.Class;
securityContexts = [SecurityContext.NONE];
} else if (parts[0] == STYLE_PREFIX) {
Expand Down
8 changes: 8 additions & 0 deletions packages/compiler/test/render3/r3_template_transform_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ describe('R3 template transform', () => {
]);
});

it('should parse bound classes with dots in the name', () => {
expectFromHtml('<div [class.px-3.5]="v" [class.a.b.c]="w"></div>').toEqual([
['Element', 'div'],
['BoundAttribute', BindingType.Class, 'px-3.5', 'v'],
['BoundAttribute', BindingType.Class, 'a.b.c', 'w'],
]);
});

it('should parse mixed case bound styles', () => {
expectFromHtml('<div [style.someStyle]="v"></div>').toEqual([
['Element', 'div'],
Expand Down
27 changes: 27 additions & 0 deletions packages/core/test/acceptance/styling_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1617,6 +1617,33 @@ describe('styling', () => {
expect(element.classList.contains('dir-two')).toBeTruthy();
});

it('should support class bindings with dots in the class name', () => {
@Directive({
selector: '[dir]',
host: {'[class.py-2.5]': 'exp'},
})
class Dir {
exp = true;
}

@Component({
template: `<div dir [class.px-3.5]="exp"></div>`,
imports: [Dir],
})
class Cmp {
exp = true;
}

const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();

const element = fixture.nativeElement.querySelector('div');
expect(element.classList.contains('px-3.5')).toBeTrue();
expect(element.classList.contains('py-2.5')).toBeTrue();
expect(element.classList.contains('px-3')).toBeFalse();
expect(element.classList.contains('py-2')).toBeFalse();
});

it('should not write empty style values to the DOM', () => {
@Component({
template: `
Expand Down
30 changes: 30 additions & 0 deletions packages/language-service/test/quick_info_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,14 @@ describe('quick info', () => {
});
});

it('should work in class bindings with dots in the class name', () => {
expectQuickInfo({
templateOverride: `<div [class.px-3.5]="tit¦le"></div>`,
expectedSpanText: 'title',
expectedDisplayString: '(property) AppCmp.title: string',
});
});

it('should work for accessed function calls', () => {
expectQuickInfo({
templateOverride: `<div (click)="someObject.some¦Method()"></div>`,
Expand Down Expand Up @@ -1050,6 +1058,28 @@ describe('quick info', () => {
});
});

it('should handle host class binding with dots in the class name', () => {
const source = `
import {Component} from '@angular/core';

@Component({
template: '',
selector: 'app-cmp',
host: {'[class.py-2.5]': 'isActive'}
})
export class AppCmp {
isActive = true;
}
`;

expectHostBindingsQuickInfo({
source,
moveTo: `'[class.py-2.5]': 'isAc¦tive'`,
expectedSpanText: 'isActive',
expectedDisplayString: '(property) AppCmp.isActive: boolean',
});
});

it('should handle host listener', () => {
const source = `
import {Component} from '@angular/core';
Expand Down
Loading