Skip to content

Commit d94989c

Browse files
Add role format handler (#3165)
* add role format handler * one line change to rerun checks
1 parent 0412f9a commit d94989c

File tree

8 files changed

+207
-2
lines changed

8 files changed

+207
-2
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { RoleFormat } from 'roosterjs-content-model-types';
2+
import type { FormatHandler } from '../FormatHandler';
3+
4+
/**
5+
* @internal
6+
*/
7+
export const roleFormatHandler: FormatHandler<RoleFormat> = {
8+
parse: (format, element) => {
9+
const role = element.getAttribute('role');
10+
11+
if (role) {
12+
format.role = role;
13+
}
14+
},
15+
apply: (format, element) => {
16+
if (format.role) {
17+
element.setAttribute('role', format.role);
18+
}
19+
},
20+
};

packages/roosterjs-content-model-dom/lib/formatHandlers/defaultFormatHandlers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { listLevelThreadFormatHandler } from './list/listLevelThreadFormatHandle
2424
import { listStyleFormatHandler } from './list/listStyleFormatHandler';
2525
import { marginFormatHandler } from './block/marginFormatHandler';
2626
import { paddingFormatHandler } from './block/paddingFormatHandler';
27+
import { roleFormatHandler } from './common/roleFormatHandler';
2728
import { sizeFormatHandler } from './common/sizeFormatHandler';
2829
import { strikeFormatHandler } from './segment/strikeFormatHandler';
2930
import { superOrSubScriptFormatHandler } from './segment/superOrSubScriptFormatHandler';
@@ -79,6 +80,7 @@ const defaultFormatHandlerMap: FormatHandlers = {
7980
listStyle: listStyleFormatHandler,
8081
margin: marginFormatHandler,
8182
padding: paddingFormatHandler,
83+
role: roleFormatHandler,
8284
size: sizeFormatHandler,
8385
strike: strikeFormatHandler,
8486
superOrSubScript: superOrSubScriptFormatHandler,
@@ -179,6 +181,7 @@ export const defaultFormatKeysPerCategory: {
179181
'tableLayout',
180182
'textColor',
181183
'direction',
184+
'role',
182185
],
183186
tableBorder: ['borderBox', 'tableSpacing'],
184187
tableCellBorder: ['borderBox'],

packages/roosterjs-content-model-dom/test/formatHandlers/block/displayFormatHandlerTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createDomToModelContext } from '../../../lib/domToModel/context/createDomToModelContext';
22
import { createModelToDomContext } from '../../../lib/modelToDom/context/createModelToDomContext';
3-
import { DisplayFormat, DomToModelContext, ModelToDomContext } from 'roosterjs-content-model-types';
43
import { displayFormatHandler } from '../../../lib/formatHandlers/block/displayFormatHandler';
4+
import { DisplayFormat, DomToModelContext, ModelToDomContext } from 'roosterjs-content-model-types';
55

66
describe('displayFormatHandler.parse', () => {
77
let div: HTMLElement;
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { DomToModelContext, ModelToDomContext, RoleFormat } from 'roosterjs-content-model-types';
2+
import { roleFormatHandler } from '../../../lib/formatHandlers/common/roleFormatHandler';
3+
import { createDomToModelContext } from '../../../lib/domToModel/context/createDomToModelContext';
4+
import { createModelToDomContext } from '../../../lib/modelToDom/context/createModelToDomContext';
5+
6+
describe('roleFormatHandler.parse', () => {
7+
let div: HTMLElement;
8+
let format: RoleFormat;
9+
let context: DomToModelContext;
10+
11+
beforeEach(() => {
12+
div = document.createElement('div');
13+
format = {};
14+
context = createDomToModelContext();
15+
});
16+
17+
it('No role', () => {
18+
roleFormatHandler.parse(format, div, context, {});
19+
expect(format).toEqual({});
20+
});
21+
22+
it('has role', () => {
23+
div.setAttribute('role', 'button');
24+
roleFormatHandler.parse(format, div, context, {});
25+
expect(format).toEqual({
26+
role: 'button',
27+
});
28+
});
29+
30+
it('has role with different value', () => {
31+
div.setAttribute('role', 'tabpanel');
32+
roleFormatHandler.parse(format, div, context, {});
33+
expect(format).toEqual({
34+
role: 'tabpanel',
35+
});
36+
});
37+
38+
it('has empty role', () => {
39+
div.setAttribute('role', '');
40+
roleFormatHandler.parse(format, div, context, {});
41+
expect(format).toEqual({});
42+
});
43+
44+
it('table with role="table"', () => {
45+
const table = document.createElement('table');
46+
table.setAttribute('role', 'table');
47+
roleFormatHandler.parse(format, table, context, {});
48+
expect(format).toEqual({
49+
role: 'table',
50+
});
51+
});
52+
53+
it('table with role="grid"', () => {
54+
const table = document.createElement('table');
55+
table.setAttribute('role', 'grid');
56+
roleFormatHandler.parse(format, table, context, {});
57+
expect(format).toEqual({
58+
role: 'grid',
59+
});
60+
});
61+
62+
it('table with role="presentation"', () => {
63+
const table = document.createElement('table');
64+
table.setAttribute('role', 'presentation');
65+
roleFormatHandler.parse(format, table, context, {});
66+
expect(format).toEqual({
67+
role: 'presentation',
68+
});
69+
});
70+
71+
it('table with role="treegrid"', () => {
72+
const table = document.createElement('table');
73+
table.setAttribute('role', 'treegrid');
74+
roleFormatHandler.parse(format, table, context, {});
75+
expect(format).toEqual({
76+
role: 'treegrid',
77+
});
78+
});
79+
});
80+
81+
describe('roleFormatHandler.apply', () => {
82+
let div: HTMLElement;
83+
let format: RoleFormat;
84+
let context: ModelToDomContext;
85+
86+
beforeEach(() => {
87+
div = document.createElement('div');
88+
format = {};
89+
context = createModelToDomContext();
90+
});
91+
92+
it('No role', () => {
93+
roleFormatHandler.apply(format, div, context);
94+
expect(div.outerHTML).toBe('<div></div>');
95+
});
96+
97+
it('Has role', () => {
98+
format.role = 'button';
99+
roleFormatHandler.apply(format, div, context);
100+
expect(div.outerHTML).toBe('<div role="button"></div>');
101+
});
102+
103+
it('Has role with different value', () => {
104+
format.role = 'tabpanel';
105+
roleFormatHandler.apply(format, div, context);
106+
expect(div.outerHTML).toBe('<div role="tabpanel"></div>');
107+
});
108+
109+
it('Role applied to different element types', () => {
110+
format.role = 'navigation';
111+
const nav = document.createElement('nav');
112+
roleFormatHandler.apply(format, nav, context);
113+
expect(nav.outerHTML).toBe('<nav role="navigation"></nav>');
114+
});
115+
116+
it('Apply role="table" to table element', () => {
117+
format.role = 'table';
118+
const table = document.createElement('table');
119+
roleFormatHandler.apply(format, table, context);
120+
expect(table.outerHTML).toBe('<table role="table"></table>');
121+
});
122+
123+
it('Apply role="grid" to table element', () => {
124+
format.role = 'grid';
125+
const table = document.createElement('table');
126+
roleFormatHandler.apply(format, table, context);
127+
expect(table.outerHTML).toBe('<table role="grid"></table>');
128+
});
129+
130+
it('Apply role="presentation" to table element', () => {
131+
format.role = 'presentation';
132+
const table = document.createElement('table');
133+
roleFormatHandler.apply(format, table, context);
134+
expect(table.outerHTML).toBe('<table role="presentation"></table>');
135+
});
136+
137+
it('Apply role="treegrid" to table element', () => {
138+
format.role = 'treegrid';
139+
const table = document.createElement('table');
140+
roleFormatHandler.apply(format, table, context);
141+
expect(table.outerHTML).toBe('<table role="treegrid"></table>');
142+
});
143+
144+
it('Apply role to table cell elements', () => {
145+
format.role = 'gridcell';
146+
const td = document.createElement('td');
147+
roleFormatHandler.apply(format, td, context);
148+
expect(td.outerHTML).toBe('<td role="gridcell"></td>');
149+
});
150+
151+
it('Apply role to table header elements', () => {
152+
format.role = 'columnheader';
153+
const th = document.createElement('th');
154+
roleFormatHandler.apply(format, th, context);
155+
expect(th.outerHTML).toBe('<th role="columnheader"></th>');
156+
});
157+
158+
it('Apply role to table row elements', () => {
159+
format.role = 'row';
160+
const tr = document.createElement('tr');
161+
roleFormatHandler.apply(format, tr, context);
162+
expect(tr.outerHTML).toBe('<tr role="row"></tr>');
163+
});
164+
});

packages/roosterjs-content-model-types/lib/contentModel/format/ContentModelTableFormat.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { SpacingFormat } from './formatParts/SpacingFormat';
99
import type { TableLayoutFormat } from './formatParts/TableLayoutFormat';
1010
import type { SizeFormat } from './formatParts/SizeFormat';
1111
import type { DirectionFormat } from './formatParts/DirectionFormat';
12+
import type { RoleFormat } from './formatParts/RoleFormat';
1213

1314
/**
1415
* Format of Table
@@ -23,4 +24,5 @@ export type ContentModelTableFormat = ContentModelBlockFormat &
2324
MarginFormat &
2425
DisplayFormat &
2526
TableLayoutFormat &
26-
SizeFormat;
27+
SizeFormat &
28+
RoleFormat;

packages/roosterjs-content-model-types/lib/contentModel/format/FormatHandlerTypeMap.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { ListStyleFormat } from './formatParts/ListStyleFormat';
2222
import type { ListThreadFormat } from './formatParts/ListThreadFormat';
2323
import type { MarginFormat } from './formatParts/MarginFormat';
2424
import type { PaddingFormat } from './formatParts/PaddingFormat';
25+
import type { RoleFormat } from './formatParts/RoleFormat';
2526
import type { SizeFormat } from './formatParts/SizeFormat';
2627
import type { SpacingFormat } from './formatParts/SpacingFormat';
2728
import type { StrikeFormat } from './formatParts/StrikeFormat';
@@ -165,6 +166,11 @@ export interface FormatHandlerTypeMap {
165166
*/
166167
padding: PaddingFormat;
167168

169+
/**
170+
* Format for RoleFormat
171+
*/
172+
role: RoleFormat;
173+
168174
/**
169175
* Format for SizeFormat
170176
*/
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Format of Aria role
3+
*/
4+
export type RoleFormat = {
5+
/**
6+
* Role of this element
7+
*/
8+
role?: string;
9+
};

packages/roosterjs-content-model-types/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export { FloatFormat } from './contentModel/format/formatParts/FloatFormat';
5959
export { EntityInfoFormat } from './contentModel/format/formatParts/EntityInfoFormat';
6060
export { UndeletableFormat } from './contentModel/format/formatParts/UndeletableFormat';
6161
export { ImageStateFormat } from './contentModel/format/formatParts/ImageStateFormat';
62+
export { RoleFormat } from './contentModel/format/formatParts/RoleFormat';
6263

6364
export { DatasetFormat, ReadonlyDatasetFormat } from './contentModel/format/metadata/DatasetFormat';
6465
export { TableMetadataFormat } from './contentModel/format/metadata/TableMetadataFormat';

0 commit comments

Comments
 (0)