@@ -134,6 +134,7 @@ export class EntityTable extends LitElement implements RestAction {
134134 private _userSelectedGroup : boolean = false ;
135135 private _touchStartX = 0 ;
136136 private _touchStartY = 0 ;
137+ private _swipeLocked : boolean | null = null ;
137138 private _actionRenderer = new ActionRenderer ( ) ;
138139 private _basePath = getBasePath ( ) ;
139140 private groups : groupConfig [ ] = [ ]
@@ -150,17 +151,20 @@ export class EntityTable extends LitElement implements RestAction {
150151 protected updated ( ) {
151152 if ( this . _navScrollInitialized ) return ;
152153 const nav = this . shadowRoot ?. querySelector ( '.nav-group' ) as HTMLElement | null ;
153- if ( ! nav ) return ;
154+ const sentinel = this . shadowRoot ?. querySelector ( '.nav-sentinel' ) as HTMLElement | null ;
155+ const layout = this . shadowRoot ?. querySelector ( '.layout' ) as HTMLElement | null ;
156+ if ( ! nav || ! sentinel || ! layout ) return ;
154157 this . _navScrollInitialized = true ;
155- requestAnimationFrame ( ( ) => {
156- const headerHeight = parseFloat (
157- document . documentElement . style . getPropertyValue ( '--header-height' ) || '64'
158- ) ;
159- const threshold = Math . max ( 0 , nav . getBoundingClientRect ( ) . top + window . scrollY - headerHeight ) ;
160- const check = ( ) => nav . classList . toggle ( 'is-stuck' , window . scrollY > threshold ) ;
161- window . addEventListener ( 'scroll' , check , { passive : true } ) ;
162- check ( ) ;
163- } ) ;
158+ const headerHeight = parseFloat (
159+ document . documentElement . style . getPropertyValue ( '--header-height' ) || '64'
160+ ) ;
161+ new IntersectionObserver ( ( [ entry ] ) => {
162+ nav . classList . toggle ( 'is-stuck' , ! entry . isIntersecting ) ;
163+ } , {
164+ rootMargin : `-${ headerHeight } px 0px 0px 0px` ,
165+ threshold : 1.0 ,
166+ } ) . observe ( sentinel ) ;
167+ layout . addEventListener ( 'touchmove' , this . _onTouchMove , { passive : false } ) ;
164168 }
165169
166170 connectedCallback ( ) {
@@ -407,30 +411,78 @@ export class EntityTable extends LitElement implements RestAction {
407411 return sortedGroupedMap ;
408412 }
409413
414+ private _selectGroup ( name : string , animDirection : - 1 | 0 | 1 = 0 ) {
415+ this . activeGroup = name ;
416+ this . _userSelectedGroup = true ;
417+ this . searchQuery = '' ;
418+ if ( animDirection === 0 ) return ;
419+ requestAnimationFrame ( ( ) => {
420+ const ca = this . shadowRoot ?. querySelector ( '.content-area' ) as HTMLElement | null ;
421+ if ( ! ca ) return ;
422+ const cls = animDirection > 0 ? 'swipe-in-right' : 'swipe-in-left' ;
423+ ca . classList . remove ( 'swipe-in-right' , 'swipe-in-left' ) ;
424+ void ca . offsetWidth ;
425+ ca . classList . add ( cls ) ;
426+ ca . addEventListener ( 'animationend' , ( ) => ca . classList . remove ( cls ) , { once : true } ) ;
427+ } ) ;
428+ }
429+
410430 private _swipeToGroup ( direction : 1 | - 1 ) {
411431 const names = Array . from ( this . _groupBy ( this . entities , "sorting_group" ) . keys ( ) ) ;
412432 const idx = names . indexOf ( this . activeGroup ) ;
413433 const next = idx + direction ;
414434 if ( next < 0 || next >= names . length ) return ;
415- this . activeGroup = names [ next ] ;
416- this . _userSelectedGroup = true ;
417- this . searchQuery = '' ;
435+ this . _selectGroup ( names [ next ] , direction ) ;
418436 requestAnimationFrame ( ( ) => {
419437 const navItems = this . shadowRoot ?. querySelectorAll < HTMLElement > ( '.nav-item' ) ;
420438 navItems ?. [ next ] ?. scrollIntoView ( { behavior : 'smooth' , block : 'nearest' , inline : 'center' } ) ;
421439 } ) ;
422440 }
423441
442+ private _clickGroup ( name : string ) {
443+ const names = Array . from ( this . _groupBy ( this . entities , "sorting_group" ) . keys ( ) ) ;
444+ const dir = Math . sign ( names . indexOf ( name ) - names . indexOf ( this . activeGroup ) ) as - 1 | 0 | 1 ;
445+ this . _selectGroup ( name , dir ) ;
446+ }
447+
424448 private _onTouchStart = ( e : TouchEvent ) => {
425449 this . _touchStartX = e . touches [ 0 ] . clientX ;
426450 this . _touchStartY = e . touches [ 0 ] . clientY ;
451+ this . _swipeLocked = null ;
452+ const ca = this . shadowRoot ?. querySelector ( '.content-area' ) as HTMLElement | null ;
453+ if ( ca ) ca . style . transition = 'none' ;
454+ } ;
455+
456+ private _onTouchMove = ( e : TouchEvent ) => {
457+ const dx = e . touches [ 0 ] . clientX - this . _touchStartX ;
458+ const dy = e . touches [ 0 ] . clientY - this . _touchStartY ;
459+ if ( this . _swipeLocked === null ) {
460+ if ( Math . abs ( dx ) > 8 || Math . abs ( dy ) > 8 )
461+ this . _swipeLocked = Math . abs ( dx ) > Math . abs ( dy ) ;
462+ return ;
463+ }
464+ if ( ! this . _swipeLocked ) return ;
465+ e . preventDefault ( ) ;
466+ const names = Array . from ( this . _groupBy ( this . entities , "sorting_group" ) . keys ( ) ) ;
467+ const idx = names . indexOf ( this . activeGroup ) ;
468+ const atBoundary = ( dx < 0 && idx >= names . length - 1 ) || ( dx > 0 && idx <= 0 ) ;
469+ const ca = this . shadowRoot ?. querySelector ( '.content-area' ) as HTMLElement | null ;
470+ if ( ca ) {
471+ ca . style . transition = 'none' ;
472+ ca . style . transform = `translateX(${ dx * ( atBoundary ? 0.12 : 0.35 ) } px)` ;
473+ }
427474 } ;
428475
429476 private _onTouchEnd = ( e : TouchEvent ) => {
430477 const dx = e . changedTouches [ 0 ] . clientX - this . _touchStartX ;
431478 const dy = e . changedTouches [ 0 ] . clientY - this . _touchStartY ;
432- if ( Math . abs ( dx ) > Math . abs ( dy ) && Math . abs ( dx ) > 50 ) {
479+ const ca = this . shadowRoot ?. querySelector ( '.content-area' ) as HTMLElement | null ;
480+ if ( this . _swipeLocked && Math . abs ( dx ) > Math . abs ( dy ) && Math . abs ( dx ) > 50 ) {
481+ if ( ca ) { ca . style . transition = 'none' ; ca . style . transform = '' ; }
433482 this . _swipeToGroup ( dx < 0 ? 1 : - 1 ) ;
483+ } else if ( ca ?. style . transform ) {
484+ ca . style . transition = 'transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94)' ;
485+ ca . style . transform = '' ;
434486 }
435487 } ;
436488
@@ -462,7 +514,8 @@ export class EntityTable extends LitElement implements RestAction {
462514
463515 return html `
464516 < div class ="layout " @touchstart ="${ this . _onTouchStart } " @touchend ="${ this . _onTouchEnd } ">
465- < nav class ="nav-group " @touchstart ="${ ( e : Event ) => e . stopPropagation ( ) } " @touchend ="${ ( e : Event ) => e . stopPropagation ( ) } ">
517+ < div class ="nav-sentinel " aria-hidden ="true "> </ div >
518+ < nav class ="nav-group " @touchstart ="${ ( e : Event ) => e . stopPropagation ( ) } " @touchmove ="${ ( e : Event ) => e . stopPropagation ( ) } " @touchend ="${ ( e : Event ) => e . stopPropagation ( ) } ">
466519 < div class ="nav-search-wrap ">
467520 < iconify-icon icon ="mdi:magnify " height ="14px " class ="nav-search-icon "> </ iconify-icon >
468521 < input
@@ -479,16 +532,18 @@ export class EntityTable extends LitElement implements RestAction {
479532 ` : nothing }
480533 </ div >
481534 < div class ="nav-search-divider "> </ div >
482- ${ elems . map (
483- ( group ) => html `
484- < button
485- class ="nav-item ${ ! isSearching && this . activeGroup === group . name ? "active" : "" } "
486- @click ="${ ( ) => { this . activeGroup = group . name ; this . _userSelectedGroup = true ; this . searchQuery = '' ; } } "
487- >
488- ${ group . name || EntityTable . ENTITY_UNDEFINED }
489- </ button >
490- `
491- ) }
535+ < div class ="nav-items-scroll ">
536+ ${ elems . map (
537+ ( group ) => html `
538+ < button
539+ class ="nav-item ${ ! isSearching && this . activeGroup === group . name ? "active" : "" } "
540+ @click ="${ ( ) => this . _clickGroup ( group . name ) } "
541+ >
542+ ${ group . name || EntityTable . ENTITY_UNDEFINED }
543+ </ button >
544+ `
545+ ) }
546+ </ div >
492547 </ nav >
493548 < div class ="content-area ">
494549 ${ isSearching ? html `
0 commit comments