Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcd6d875

Browse files
committed
feat(core): render ARIA property bindings as attributes
Allow binding to ARIA attributes using property binding syntax _without_the `attr.` prefix. For example, `[aria-label]="expr"` is now valid, andequivalent to `[ariaLabel]="expr"`. Both examples bind to either amatching input or the `aria-label` HTML attribute, rather than the`ariaLabel` DOM property.Binding ARIA properties as attributes will ensure they are renderedcorrectly on the server, where the emulated DOM may not correctlyreflect ARIA properties as attributes.Reuse the DOM schema registry from the compiler to map property names intype check blocks.
1 parent6adaf01 commitcd6d875

File tree

24 files changed

+544
-79
lines changed

24 files changed

+544
-79
lines changed

‎packages/compiler-cli/src/ngtsc/typecheck/src/dom.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {makeTemplateDiagnostic} from '../diagnostics';
2020

2121
import{TypeCheckSourceResolver}from'./tcb_util';
2222

23-
constREGISTRY=newDomElementSchemaRegistry();
23+
exportconstREGISTRY=newDomElementSchemaRegistry();
2424
constREMOVE_XHTML_REGEX=/^:xhtml:/;
2525

2626
/**

‎packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import {
7070
wrapForDiagnostics,
7171
wrapForTypeChecker,
7272
}from'./diagnostics';
73-
import{DomSchemaChecker}from'./dom';
73+
import{DomSchemaChecker,REGISTRY}from'./dom';
7474
import{Environment}from'./environment';
7575
import{astToTypescript,getAnyExpression}from'./expression';
7676
import{OutOfBandDiagnosticRecorder}from'./oob';
@@ -1206,7 +1206,7 @@ class TcbDomSchemaCheckerOp extends TcbOp {
12061206

12071207
if(isPropertyBinding&&binding.name!=='style'&&binding.name!=='class'){
12081208
// A direct binding to a property.
1209-
constpropertyName=ATTR_TO_PROP.get(binding.name)??binding.name;
1209+
constpropertyName=REGISTRY.getMappedPropName(binding.name);
12101210

12111211
if(isTemplateElement){
12121212
this.tcb.domSchemaChecker.checkTemplateElementProperty(
@@ -1418,21 +1418,6 @@ class TcbComponentNodeOp extends TcbOp {
14181418
}
14191419
}
14201420

1421-
/**
1422-
* Mapping between attributes names that don't correspond to their element property names.
1423-
* Note: this mapping has to be kept in sync with the equally named mapping in the runtime.
1424-
*/
1425-
constATTR_TO_PROP=newMap(
1426-
Object.entries({
1427-
'class':'className',
1428-
'for':'htmlFor',
1429-
'formaction':'formAction',
1430-
'innerHtml':'innerHTML',
1431-
'readonly':'readOnly',
1432-
'tabindex':'tabIndex',
1433-
}),
1434-
);
1435-
14361421
/**
14371422
* A `TcbOp` which generates code to check "unclaimed inputs" - bindings on an element which were
14381423
* not attributed to any directive or component, and are instead processed against the HTML element
@@ -1481,7 +1466,7 @@ class TcbUnclaimedInputsOp extends TcbOp {
14811466
elId=this.scope.resolve(this.target);
14821467
}
14831468
// A direct binding to a property.
1484-
constpropertyName=ATTR_TO_PROP.get(binding.name)??binding.name;
1469+
constpropertyName=REGISTRY.getMappedPropName(binding.name);
14851470
constprop=ts.factory.createElementAccessExpression(
14861471
elId,
14871472
ts.factory.createStringLiteral(propertyName),

‎packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js

Lines changed: 101 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,86 @@
1+
/****************************************************************************************************
2+
* PARTIAL FILE: aria_dom_properties.js
3+
****************************************************************************************************/
4+
import{Component}from'@angular/core';
5+
import*asi0from"@angular/core";
6+
exportclassMyComponent{
7+
constructor(){
8+
this.disabled='';
9+
this.readonly='';
10+
this.label='';
11+
}
12+
}
13+
MyComponent.ɵfac=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,deps:[],target:i0.ɵɵFactoryTarget.Component});
14+
MyComponent.ɵcmp=i0.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:MyComponent,isStandalone:true,selector:"ng-component",ngImport:i0,template:`
15+
<input [attr.aria-disabled]="disabled" [aria-readonly]="readonly" [ariaLabel]="label">
16+
`,isInline:true});
17+
i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,decorators:[{
18+
type:Component,
19+
args:[{
20+
template:`
21+
<input [attr.aria-disabled]="disabled" [aria-readonly]="readonly" [ariaLabel]="label">
22+
`,
23+
}]
24+
}]});
25+
26+
/****************************************************************************************************
27+
* PARTIAL FILE: aria_dom_properties.d.ts
28+
****************************************************************************************************/
29+
import*asi0from"@angular/core";
30+
exportdeclareclassMyComponent{
31+
disabled:string;
32+
readonly:string;
33+
label:string;
34+
staticɵfac:i0.ɵɵFactoryDeclaration<MyComponent,never>;
35+
staticɵcmp:i0.ɵɵComponentDeclaration<MyComponent,"ng-component",never,{},{},never,never,true,never>;
36+
}
37+
38+
/****************************************************************************************************
39+
* PARTIAL FILE: aria_properties.js
40+
****************************************************************************************************/
41+
import{ Component, Directive}from'@angular/core';
42+
import*asi0from"@angular/core";
43+
classMyDir{
44+
}
45+
MyDir.ɵfac=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyDir,deps:[],target:i0.ɵɵFactoryTarget.Directive});
46+
MyDir.ɵdir=i0.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:MyDir,isStandalone:true,selector:"[myDir]",ngImport:i0});
47+
i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyDir,decorators:[{
48+
type:Directive,
49+
args:[{selector:'[myDir]'}]
50+
}]});
51+
exportclassMyComponent{
52+
constructor(){
53+
this.disabled='';
54+
this.readonly='';
55+
this.label='';
56+
}
57+
}
58+
MyComponent.ɵfac=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,deps:[],target:i0.ɵɵFactoryTarget.Component});
59+
MyComponent.ɵcmp=i0.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:MyComponent,isStandalone:true,selector:"ng-component",ngImport:i0,template:`
60+
<input myDir [attr.aria-disabled]="disabled" [aria-readonly]="readonly" [ariaLabel]="label">
61+
`,isInline:true,dependencies:[{kind:"directive",type:MyDir,selector:"[myDir]"}]});
62+
i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,decorators:[{
63+
type:Component,
64+
args:[{
65+
template:`
66+
<input myDir [attr.aria-disabled]="disabled" [aria-readonly]="readonly" [ariaLabel]="label">
67+
`,
68+
imports:[MyDir],
69+
}]
70+
}]});
71+
72+
/****************************************************************************************************
73+
* PARTIAL FILE: aria_properties.d.ts
74+
****************************************************************************************************/
75+
import*asi0from"@angular/core";
76+
exportdeclareclassMyComponent{
77+
disabled:string;
78+
readonly:string;
79+
label:string;
80+
staticɵfac:i0.ɵɵFactoryDeclaration<MyComponent,never>;
81+
staticɵcmp:i0.ɵɵComponentDeclaration<MyComponent,"ng-component",never,{},{},never,never,true,never>;
82+
}
83+
184
/****************************************************************************************************
285
* PARTIAL FILE: bind.js
386
****************************************************************************************************/
@@ -349,26 +432,25 @@ import * as i0 from "@angular/core";
349432
exportclassButtonDir{
350433
}
351434
ButtonDir.ɵfac=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:ButtonDir,deps:[],target:i0.ɵɵFactoryTarget.Directive});
352-
ButtonDir.ɵdir=i0.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:ButtonDir,isStandalone:false,selector:"button",inputs:{al:["aria-label","al"]},ngImport:i0});
435+
ButtonDir.ɵdir=i0.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:ButtonDir,isStandalone:false,selector:"button",inputs:{label:"label"},ngImport:i0});
353436
i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:ButtonDir,decorators:[{
354437
type:Directive,
355438
args:[{
356439
selector:'button',
357-
standalone:false
440+
standalone:false,
358441
}]
359-
}],propDecorators:{al:[{
360-
type:Input,
361-
args:['aria-label']
442+
}],propDecorators:{label:[{
443+
type:Input
362444
}]}});
363445
exportclassMyComponent{
364446
}
365447
MyComponent.ɵfac=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,deps:[],target:i0.ɵɵFactoryTarget.Component});
366-
MyComponent.ɵcmp=i0.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:MyComponent,isStandalone:false,selector:"ng-component",ngImport:i0,template:'<button [title]="1" [attr.id]="2" [tabindex]="3"aria-label="{{1 + 3}}"></button>',isInline:true,dependencies:[{kind:"directive",type:ButtonDir,selector:"button",inputs:["aria-label"]}]});
448+
MyComponent.ɵcmp=i0.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:MyComponent,isStandalone:false,selector:"ng-component",ngImport:i0,template:'<button [title]="1" [attr.id]="2" [tabindex]="3" label="{{1 + 3}}"></button>',isInline:true,dependencies:[{kind:"directive",type:ButtonDir,selector:"button",inputs:["label"]}]});
367449
i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,decorators:[{
368450
type:Component,
369451
args:[{
370-
template:'<button [title]="1" [attr.id]="2" [tabindex]="3"aria-label="{{1 + 3}}"></button>',
371-
standalone:false
452+
template:'<button [title]="1" [attr.id]="2" [tabindex]="3" label="{{1 + 3}}"></button>',
453+
standalone:false,
372454
}]
373455
}]});
374456
exportclassMyMod{
@@ -386,9 +468,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
386468
****************************************************************************************************/
387469
import*asi0from"@angular/core";
388470
exportdeclareclassButtonDir{
389-
al:any;
471+
label:any;
390472
staticɵfac:i0.ɵɵFactoryDeclaration<ButtonDir,never>;
391-
staticɵdir:i0.ɵɵDirectiveDeclaration<ButtonDir,"button",never,{"al":{"alias":"aria-label";"required":false;};},{},never,never,false,never>;
473+
staticɵdir:i0.ɵɵDirectiveDeclaration<ButtonDir,"button",never,{"label":{"alias":"label";"required":false;};},{},never,never,false,never>;
392474
}
393475
exportdeclareclassMyComponent{
394476
staticɵfac:i0.ɵɵFactoryDeclaration<MyComponent,never>;
@@ -408,26 +490,25 @@ import * as i0 from "@angular/core";
408490
exportclassButtonDir{
409491
}
410492
ButtonDir.ɵfac=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:ButtonDir,deps:[],target:i0.ɵɵFactoryTarget.Directive});
411-
ButtonDir.ɵdir=i0.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:ButtonDir,isStandalone:false,selector:"button",inputs:{al:["aria-label","al"]},ngImport:i0});
493+
ButtonDir.ɵdir=i0.ɵɵngDeclareDirective({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:ButtonDir,isStandalone:false,selector:"button",inputs:{label:"label"},ngImport:i0});
412494
i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:ButtonDir,decorators:[{
413495
type:Directive,
414496
args:[{
415497
selector:'button',
416-
standalone:false
498+
standalone:false,
417499
}]
418-
}],propDecorators:{al:[{
419-
type:Input,
420-
args:['aria-label']
500+
}],propDecorators:{label:[{
501+
type:Input
421502
}]}});
422503
exportclassMyComponent{
423504
}
424505
MyComponent.ɵfac=i0.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,deps:[],target:i0.ɵɵFactoryTarget.Component});
425-
MyComponent.ɵcmp=i0.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:MyComponent,isStandalone:false,selector:"ng-component",ngImport:i0,template:'<button [title]="1" [id]="2" tabindex="{{0 + 3}}"aria-label="hello-{{1 + 3}}-{{2 + 3}}"></button>',isInline:true,dependencies:[{kind:"directive",type:ButtonDir,selector:"button",inputs:["aria-label"]}]});
506+
MyComponent.ɵcmp=i0.ɵɵngDeclareComponent({minVersion:"14.0.0",version:"0.0.0-PLACEHOLDER",type:MyComponent,isStandalone:false,selector:"ng-component",ngImport:i0,template:'<button [title]="1" [id]="2" tabindex="{{0 + 3}}" label="hello-{{1 + 3}}-{{2 + 3}}"></button>',isInline:true,dependencies:[{kind:"directive",type:ButtonDir,selector:"button",inputs:["label"]}]});
426507
i0.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"0.0.0-PLACEHOLDER",ngImport:i0,type:MyComponent,decorators:[{
427508
type:Component,
428509
args:[{
429-
template:'<button [title]="1" [id]="2" tabindex="{{0 + 3}}"aria-label="hello-{{1 + 3}}-{{2 + 3}}"></button>',
430-
standalone:false
510+
template:'<button [title]="1" [id]="2" tabindex="{{0 + 3}}" label="hello-{{1 + 3}}-{{2 + 3}}"></button>',
511+
standalone:false,
431512
}]
432513
}]});
433514
exportclassMyMod{
@@ -445,9 +526,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
445526
****************************************************************************************************/
446527
import*asi0from"@angular/core";
447528
exportdeclareclassButtonDir{
448-
al:any;
529+
label:any;
449530
staticɵfac:i0.ɵɵFactoryDeclaration<ButtonDir,never>;
450-
staticɵdir:i0.ɵɵDirectiveDeclaration<ButtonDir,"button",never,{"al":{"alias":"aria-label";"required":false;};},{},never,never,false,never>;
531+
staticɵdir:i0.ɵɵDirectiveDeclaration<ButtonDir,"button",never,{"label":{"alias":"label";"required":false;};},{},never,never,false,never>;
451532
}
452533
exportdeclareclassMyComponent{
453534
staticɵfac:i0.ɵɵFactoryDeclaration<MyComponent,never>;

‎packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/TEST_CASES.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,32 @@
11
{
22
"$schema":"../../test_case_schema.json",
33
"cases": [
4+
{
5+
"description":"should generate attribute instructions for DOM-only ARIA bindings",
6+
"inputFiles": [
7+
"aria_dom_properties.ts"
8+
],
9+
"expectations": [
10+
{
11+
"files": [
12+
"aria_dom_properties.js"
13+
]
14+
}
15+
]
16+
},
17+
{
18+
"description":"should generate ariaProperty instructions for ARIA bindings",
19+
"inputFiles": [
20+
"aria_properties.ts"
21+
],
22+
"expectations": [
23+
{
24+
"files": [
25+
"aria_properties.js"
26+
]
27+
}
28+
]
29+
},
430
{
531
"description":"should generate bind instruction",
632
"inputFiles": [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if(rf&2){
2+
$r3$.ɵɵattribute("aria-readonly",ctx.readonly)("aria-label",ctx.label)("aria-disabled",ctx.disabled);
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import{Component}from'@angular/core';
2+
3+
@Component({
4+
template:`
5+
<input [attr.aria-disabled]="disabled" [aria-readonly]="readonly" [ariaLabel]="label">
6+
`,
7+
})
8+
exportclassMyComponent{
9+
disabled='';
10+
readonly='';
11+
label='';
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
if(rf&2){
2+
$r3$.ɵɵariaProperty("aria-readonly",ctx.readonly)("ariaLabel",ctx.label);
3+
$r3$.ɵɵattribute("aria-disabled",ctx.disabled);
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import{Component,Directive}from'@angular/core';
2+
3+
@Directive({selector:'[myDir]'})
4+
classMyDir{}
5+
6+
@Component({
7+
template:`
8+
<input myDir [attr.aria-disabled]="disabled" [aria-readonly]="readonly" [ariaLabel]="label">
9+
`,
10+
imports:[MyDir],
11+
})
12+
exportclassMyComponent{
13+
disabled='';
14+
readonly='';
15+
label='';
16+
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
template:functionMyComponent_Template(rf,ctx){
22
33
if(rf&2){
4-
$r3$.ɵɵproperty("tabindex",$r3$.ɵɵinterpolate(0+3))("aria-label",$r3$.ɵɵinterpolate2("hello-",1+3,"-",2+3))("title",1)("id",2);
4+
$r3$.ɵɵproperty("tabindex",$r3$.ɵɵinterpolate(0+3))("label",$r3$.ɵɵinterpolate2("hello-",1+3,"-",2+3))("title",1)("id",2);
55
}
66
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
import{Component,Directive,Input,NgModule}from'@angular/core';
22

33
@Directive({
4-
selector:'button',
5-
standalone:false
4+
selector:'button',
5+
standalone:false,
66
})
77
exportclassButtonDir{
8-
@Input('aria-label')al!:any;
8+
@Input()label!:any;
99
}
1010

11-
1211
@Component({
13-
template:'<button [title]="1" [id]="2" tabindex="{{0 + 3}}" aria-label="hello-{{1 + 3}}-{{2 + 3}}"></button>',
14-
standalone:false
12+
template:
13+
'<button [title]="1" [id]="2" tabindex="{{0 + 3}}" label="hello-{{1 + 3}}-{{2 + 3}}"></button>',
14+
standalone:false,
1515
})
16-
exportclassMyComponent{
17-
}
16+
exportclassMyComponent{}
1817

1918
@NgModule({declarations:[ButtonDir,MyComponent]})
20-
exportclassMyMod{
21-
}
19+
exportclassMyMod{}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp