@@ -582,7 +582,7 @@ export class SpeechExplorer
582582 this . FocusOut ( null ) ;
583583 } else {
584584 this . stopEvent ( event ) ;
585- this . refocus = this . firstNode ( this . node ) ;
585+ this . refocus = this . rootNode ( ) ;
586586 this . Start ( ) ;
587587 }
588588 }
@@ -649,7 +649,7 @@ export class SpeechExplorer
649649 * Select top-level of expression
650650 */
651651 protected homeKey ( ) {
652- this . setCurrent ( this . firstNode ( this . node ) ) ;
652+ this . setCurrent ( this . rootNode ( ) ) ;
653653 }
654654
655655 /**
@@ -673,7 +673,7 @@ export class SpeechExplorer
673673 protected moveUp ( shift : boolean ) : boolean | void {
674674 return shift
675675 ? this . moveToNeighborCell ( - 1 , 0 )
676- : this . moveTo ( this . current . parentElement . closest ( nav ) ) ;
676+ : this . moveTo ( this . getParent ( this . current ) ) ;
677677 }
678678
679679 /**
@@ -760,7 +760,7 @@ export class SpeechExplorer
760760 protected prevMark ( ) {
761761 if ( this . currentMark < 0 ) {
762762 if ( this . marks . length === 0 ) {
763- this . setCurrent ( this . lastMark || this . firstNode ( this . node ) ) ;
763+ this . setCurrent ( this . lastMark || this . rootNode ( ) ) ;
764764 return ;
765765 }
766766 this . currentMark = this . marks . length - 1 ;
@@ -1293,15 +1293,50 @@ export class SpeechExplorer
12931293 }
12941294
12951295 /**
1296- * Get an element's first speech child.
1296+ * Get an element's first speech child. This is computed by going through the
1297+ * owns list until the first speech element is found.
12971298 *
12981299 * @param {HTMLElement } node The parent element to get a child from
12991300 * @returns {HTMLElement } The first speech child of the node
13001301 */
13011302 protected firstNode ( node : HTMLElement ) : HTMLElement {
1303+ const owns = node . getAttribute ( 'data-semantic-owns' ) ;
1304+ if ( ! owns ) {
1305+ return node . querySelector ( nav ) as HTMLElement ;
1306+ }
1307+ const ownsList = owns . split ( / / ) ;
1308+ for ( const id of ownsList ) {
1309+ const node = this . getNode ( id ) ;
1310+ if ( node ?. hasAttribute ( 'data-speech-node' ) ) {
1311+ return node ;
1312+ }
1313+ }
13021314 return node . querySelector ( nav ) as HTMLElement ;
13031315 }
13041316
1317+ /**
1318+ * Get the element's semantic root node. We compute this from the root id
1319+ * given in the semantic structure. The semantic structure is an sexp either
1320+ * of the form `0` or `(0 1 (2 ...) ...)`. We can safely assume that the root
1321+ * node contains the speech for the entire structure.
1322+ *
1323+ * If for some reason the semantic structure is not available, we return the
1324+ * first speech node found in the expression.
1325+ *
1326+ * @returns {HTMLElement } The semantic root or first speech node.
1327+ */
1328+ protected rootNode ( ) : HTMLElement {
1329+ const base = this . node . querySelector ( '[data-semantic-structure]' ) ;
1330+ if ( ! base ) {
1331+ return this . node . querySelector ( nav ) as HTMLElement ;
1332+ }
1333+ const id = base
1334+ . getAttribute ( 'data-semantic-structure' )
1335+ . split ( / / ) [ 0 ]
1336+ . replace ( '(' , '' ) ;
1337+ return this . getNode ( id ) ;
1338+ }
1339+
13051340 /**
13061341 * Navigate one step to the right on the same level.
13071342 *
@@ -1311,10 +1346,16 @@ export class SpeechExplorer
13111346 protected nextSibling ( node : HTMLElement ) : HTMLElement {
13121347 const id = this . parentId ( node ) ;
13131348 if ( ! id ) return null ;
1314- const owns = this . getNode ( id ) . getAttribute ( 'data-semantic-owns' ) ?. split ( / / ) ;
1349+ const owns = this . getNode ( id )
1350+ . getAttribute ( 'data-semantic-owns' )
1351+ ?. split ( / / ) ;
13151352 if ( ! owns ) return null ;
1316- const i = owns . indexOf ( this . nodeId ( node ) ) ;
1317- return this . getNode ( owns [ i + 1 ] ) ;
1353+ let i = owns . indexOf ( this . nodeId ( node ) ) ;
1354+ let next ;
1355+ do {
1356+ next = this . getNode ( owns [ ++ i ] ) ;
1357+ } while ( next && ! next . hasAttribute ( 'data-speech-node' ) ) ;
1358+ return next ;
13181359 }
13191360
13201361 /**
@@ -1326,10 +1367,16 @@ export class SpeechExplorer
13261367 protected prevSibling ( node : HTMLElement ) : HTMLElement {
13271368 const id = this . parentId ( node ) ;
13281369 if ( ! id ) return null ;
1329- const owns = this . getNode ( id ) . getAttribute ( 'data-semantic-owns' ) ?. split ( / / ) ;
1370+ const owns = this . getNode ( id )
1371+ . getAttribute ( 'data-semantic-owns' )
1372+ ?. split ( / / ) ;
13301373 if ( ! owns ) return null ;
1331- const i = owns . indexOf ( this . nodeId ( node ) ) ;
1332- return this . getNode ( owns [ i - 1 ] ) ;
1374+ let i = owns . indexOf ( this . nodeId ( node ) ) ;
1375+ let prev ;
1376+ do {
1377+ prev = this . getNode ( owns [ -- i ] ) ;
1378+ } while ( prev && ! prev . hasAttribute ( 'data-speech-node' ) ) ;
1379+ return prev ;
13331380 }
13341381
13351382 /**
@@ -1498,7 +1545,7 @@ export class SpeechExplorer
14981545 // current node (which creates the speech) and start the explorer.
14991546 //
15001547 const node = this . findStartNode ( ) ;
1501- this . setCurrent ( node || this . firstNode ( this . node ) , ! node ) ;
1548+ this . setCurrent ( node || this . rootNode ( ) , ! node ) ;
15021549 super . Start ( ) ;
15031550 //
15041551 // Show any needed regions
0 commit comments