Skip to content

Commit 945d705

Browse files
committed
use treegrid role for schema table
1 parent 241dc32 commit 945d705

2 files changed

Lines changed: 82 additions & 19 deletions

File tree

src/components/schema-table.js

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,12 @@ export default class SchemaTable extends LitElement {
9797
animation-duration: 0s !important;
9898
}
9999
.tr:not(.collapsed) + .object-body {
100+
visibility: visible;
100101
animation: linear 0.2s expand-height;
101102
}
102103
.tr.collapsed + .object-body {
103104
animation: linear 0.2s collapse-height;
105+
visibility: hidden;
104106
max-height: 0;
105107
}
106108
.obj-toggle {
@@ -148,12 +150,16 @@ export default class SchemaTable extends LitElement {
148150
max-width: 25%;
149151
}
150152
</style>
151-
<div class="table ${this.interactive ? 'interactive' : ''}">
153+
<div class="table ${this.interactive ? 'interactive' : ''}" role="treegrid" aria-label="JSON model" tabindex="0" @keydown="${(e) => this.treegridKeydown(e)}" @focus="${(e) => {
154+
e.target.removeAttribute('tabindex');
155+
e.target.querySelector('.td').tabIndex = 0;
156+
e.target.querySelector('.td').focus();
157+
}}">
152158
<div style = 'border:1px solid var(--light-border-color)'>
153-
<div style='display:flex; background-color: var(--bg2); padding:8px 4px; border-bottom:1px solid var(--light-border-color);'>
154-
<div class='key' part="schema-key schema-table-header" style='font-family:var(--font-regular); font-weight:bold; color:var(--fg); padding-left:${firstColumnInitialPadding}px'> Field </div>
155-
<div class='key-type' part="schema-type schema-table-header" style='font-family:var(--font-regular); font-weight:bold; color:var(--fg);'> Type </div>
156-
<div class='key-descr' part="schema-description schema-table-header" style='font-family:var(--font-regular); font-weight:bold; color:var(--fg);'> Description </div>
159+
<div style='display:flex; background-color: var(--bg2); padding:8px 4px; border-bottom:1px solid var(--light-border-color);' role="row" aria-level="1">
160+
<div class='key' role="columnheader" part="schema-key schema-table-header" style='font-family:var(--font-regular); font-weight:bold; color:var(--fg); padding-left:${firstColumnInitialPadding}px'> Field </div>
161+
<div class='key-type' role="columnheader" part="schema-type schema-table-header" style='font-family:var(--font-regular); font-weight:bold; color:var(--fg);'> Type </div>
162+
<div class='key-descr' role="columnheader" part="schema-description schema-table-header" style='font-family:var(--font-regular); font-weight:bold; color:var(--fg);'> Description </div>
157163
</div>
158164
${result || ''}
159165
</div>
@@ -245,20 +251,20 @@ export default class SchemaTable extends LitElement {
245251
const outerResult = html`
246252
${newSchemaLevel >= 0 && key
247253
? html`
248-
<div class='tr ${newSchemaLevel <= this.schemaExpandLevel ? '' : 'collapsed'} ${data['::circular'] ? 'circular-object' : 'object'} ${data['::type']}' data-obj='${keyLabel}'>
249-
<div class="td no-select key ${data['::deprecated'] ? 'deprecated' : ''}" part="schema-key"
250-
style='padding-left:${leftPadding}px; cursor: pointer' @click=${(e) => this.toggleObjectExpand(e)}>
254+
<div class='tr ${newSchemaLevel <= this.schemaExpandLevel ? '' : 'collapsed'} ${data['::circular'] ? 'circular-object' : 'object'} ${data['::type']}' data-obj='${keyLabel}' role="row" aria-level="${schemaLevel}">
255+
<div class="td no-select key ${data['::deprecated'] ? 'deprecated' : ''}" part="schema-key" tabindex="-1" role="gridcell"
256+
style='padding-left:${leftPadding}px; cursor: pointer' @click=${(e) => this.toggleObjectExpand(e)} @keydown="${(e) => { if (e.key === 'Enter') { e.target.click(); }}}" aria-expanded="true" aria-controls="${keyLabel}-obj-body">
251257
<div style="display: flex; align-items: center">
252-
${(keyLabel || keyDescr) ? html`<div class='obj-toggle' data-obj='${keyLabel}'></div>` : ''}
258+
${(keyLabel || keyDescr) ? html`<div class='obj-toggle' data-obj='${keyLabel}' aria-hidden="true"></div>` : ''}
253259
${data['::type'] === 'xxx-of-option' || key.startsWith('::OPTION')
254260
? html`<span class="key-label xxx-of-key">${keyLabel}</span><span class="${isOneOfLabel ? 'xxx-of-key' : 'xxx-of-descr'}">${keyDescr}</span>`
255261
: keyLabel.endsWith('*')
256-
? html`<span class="key-label requiredStar" style="display:inline-block;" title="Required"> ${keyLabel.substring(0, keyLabel.length - 1)}</span>`
262+
? html`<span class="key-label requiredStar" style="display:inline-block;" role="img" aria-label=" (Required)" title="Required"> ${keyLabel.substring(0, keyLabel.length - 1)}</span>`
257263
: html`<span class="key-label" style="display:inline-block;">${keyLabel === '::props' ? '' : keyLabel}</span>`
258264
}
259265
</div>
260266
</div>
261-
<div class='td key-type' part="schema-type">
267+
<div class='td key-type' part="schema-type" role="gridcell" tabindex="-1">
262268
${displaySchemaLink
263269
? html`<div class="schema-link" style="overflow: hidden; text-overflow: ellipsis" @click='${() => this.scrollToSchemaComponentByName(displaySchemaLink)}'>
264270
${dataType === 'array' ? '[' : ''}<span style="color: var(--secondary-color)">${detailObjTypeDisplay}</span>${dataType === 'array' ? ']' : ''}
@@ -267,20 +273,20 @@ export default class SchemaTable extends LitElement {
267273
}
268274
<div class="attributes" title="${flags['🆁'] && 'Read only attribute' || flags['🆆'] && 'Write only attribute' || ''}">${flags['🆁'] || flags['🆆'] || ''}</div>
269275
</div>
270-
<div class='td key-descr' part="schema-description">
276+
<div class='td key-descr' role="gridcell" tabindex="-1" part="schema-description">
271277
<span class=" m-markdown-small">${unsafeHTML(toMarkdown(displayLine))}</span>
272278
${data['::metadata']?.constraints?.length
273279
? html`<div style='display:inline-block; line-break:anywhere; margin-right:8px'><span class='bold-text'>Constraints: </span>${data['::metadata'].constraints.join(', ')}</div><br>` : ''}
274280
</div>
275281
</div>`
276282
: html`
277283
${data['::type'] === 'array' && dataType === 'array'
278-
? html`<div class='tr'> <div class='td'> ${dataType} </div> </div>`
284+
? html`<div class='tr' role="row"> <div class='td' role="gridcell" tabindex="-1"> ${dataType} </div> </div>`
279285
: ''
280286
}
281287
`
282288
}
283-
<div class='object-body'>
289+
<div class='object-body' id='${keyLabel}-obj-body'>
284290
${recursiveResult}
285291
<div>
286292
`;
@@ -301,20 +307,20 @@ export default class SchemaTable extends LitElement {
301307
}
302308

303309
const result = html`
304-
<div class = "tr">
305-
<div class="td key ${deprecated ? 'deprecated' : ''}" part="schema-key" style='padding-left:${leftPadding}px'>
310+
<div class = "tr" role="row" aria-level="${schemaLevel}">
311+
<div class="td key ${deprecated ? 'deprecated' : ''}" role="gridcell" tabindex="-1" part="schema-key" style='padding-left:${leftPadding}px'>
306312
${keyLabel?.endsWith('*')
307-
? html`<span class="key-label requiredStar" title="Required">${keyLabel.substring(0, keyLabel.length - 1)}</span>`
313+
? html`<span class="key-label requiredStar" role="img" aria-label=" (Required)" title="Required">${keyLabel.substring(0, keyLabel.length - 1)}</span>`
308314
: key.startsWith('::OPTION')
309315
? html`<span class='xxx-of-key'>${keyLabel}</span><span class="xxx-of-descr">${keyDescr}</span>`
310316
: html`${keyLabel ? html`<span class="key-label"> ${keyLabel}</span>` : html`<span class="xxx-of-descr">${schemaTitle}</span>`}`
311317
}
312318
</div>
313-
<div class='td key-type' part="schema-type">
319+
<div class='td key-type' role="gridcell" tabindex="-1" part="schema-type">
314320
<div>${dataType === 'array' ? '[' : ''}<span class="${cssType}">${format || type}</span>${dataType === 'array' ? ']' : ''}</div>
315321
<div class="attributes ${cssType}" style="font-family: var(--font-mono);" title="${readOrWriteOnly === '🆁' && 'Read only attribute' || readOrWriteOnly === '🆆' && 'Write only attribute' || ''}">${readOrWriteOnly}</div>
316322
</div>
317-
<div class='td key-descr' part="schema-description">
323+
<div class='td key-descr' role="gridcell" tabindex="-1" part="schema-description">
318324
<span class="m-markdown-small" style="vertical-align: middle;">
319325
${unsafeHTML(toMarkdown(`${`${(schemaTitle || title) ? `**${schemaTitle || title}${schemaDescription || description ? ':' : ''}**` : ''} ${schemaDescription || description}` || ''}`))}
320326
</span>
@@ -333,6 +339,62 @@ export default class SchemaTable extends LitElement {
333339
toggleObjectExpand(e) {
334340
const rowEl = e.target.closest('.tr');
335341
rowEl.classList.toggle('collapsed');
342+
e.target.setAttribute('aria-expanded', !rowEl.classList.contains('collapsed'));
343+
}
344+
345+
treegridKeydown(e) {
346+
if (e.shiftKey) {return true;}
347+
const cell = e.target;
348+
if (cell.getAttribute('role') !== 'gridcell') {return true;}
349+
const row = Array.from(cell.parentNode.children);
350+
const table = Array.from(cell.closest('.table').querySelectorAll('.tr'));
351+
const index = row.indexOf(cell);
352+
const rIndex = table.indexOf(cell.parentNode);
353+
switch (e.key) {
354+
case 'ArrowRight':
355+
if (index < row.length - 1) {
356+
row[index + 1].tabIndex = 0;
357+
row[index + 1].focus();
358+
cell.tabIndex = '-1';
359+
}
360+
break;
361+
case 'ArrowLeft':
362+
if (index > 0) {
363+
row[index - 1].tabIndex = 0;
364+
row[index - 1].focus();
365+
cell.tabIndex = '-1';
366+
}
367+
break;
368+
case 'ArrowDown':
369+
if (rIndex < table.length - 1) {
370+
table[rIndex + 1].children[index].tabIndex = 0;
371+
table[rIndex + 1].children[index].focus();
372+
cell.tabIndex = '-1';
373+
}
374+
break;
375+
case 'ArrowUp':
376+
if (rIndex > 0) {
377+
table[rIndex - 1].children[index].tabIndex = 0;
378+
table[rIndex - 1].children[index].focus();
379+
cell.tabIndex = '-1';
380+
}
381+
break;
382+
case 'Home':
383+
e.preventDefault();
384+
cell.tabIndex = '-1';
385+
row[0].tabIndex = 0;
386+
row[0].focus();
387+
break;
388+
case 'End':
389+
e.preventDefault();
390+
cell.tabIndex = '-1';
391+
row[row.length - 1].tabIndex = 0;
392+
row[row.length - 1].focus();
393+
break;
394+
default:
395+
break;
396+
}
397+
return true;
336398
}
337399
}
338400
if (!customElements.get('openapi-explorer')) {

src/styles/schema-styles.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default css`
3333
.key-descr {
3434
font-family:var(--font-regular);
3535
flex-shrink: 1;
36+
flex-grow: 1;
3637
text-overflow: ellipsis;
3738
overflow: hidden;
3839
display: none;

0 commit comments

Comments
 (0)