@@ -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}
338400if ( ! customElements . get ( 'openapi-explorer' ) ) {
0 commit comments