1+ import type { ReactTableHooks , TableInstance } from '@/components/AnalyticalTable/types/index.js' ;
12import dataLarge from '@sb/mockData/Friends500.json' ;
23import dataTree from '@sb/mockData/FriendsTree.json' ;
34import type { Meta , StoryObj } from '@storybook/react-vite' ;
45import '@ui5/webcomponents-icons/dist/delete.js' ;
56import '@ui5/webcomponents-icons/dist/edit.js' ;
67import '@ui5/webcomponents-icons/dist/settings.js' ;
78import NoDataIllustration from '@ui5/webcomponents-fiori/dist/illustrations/NoData.js' ;
8- import { useCallback , useEffect , useMemo , useReducer , useRef , useState } from 'react' ;
9+ import {
10+ FocusEventHandler ,
11+ KeyboardEventHandler ,
12+ useCallback ,
13+ useEffect ,
14+ useMemo ,
15+ useReducer ,
16+ useRef ,
17+ useState ,
18+ } from 'react' ;
919import {
1020 AnalyticalTablePopinDisplay ,
1121 AnalyticalTableScaleWidthMode ,
@@ -29,6 +39,7 @@ import { SegmentedButtonItem } from '../../webComponents/SegmentedButtonItem/ind
2939import { Select } from '../../webComponents/Select/index.js' ;
3040import { Tag } from '../../webComponents/Tag/index.js' ;
3141import { Text } from '../../webComponents/Text/index.js' ;
42+ import { Input } from '../../webComponents/input/index.js' ;
3243import { FlexBox } from '../FlexBox/index.js' ;
3344import type { AnalyticalTableColumnDefinition } from './index.js' ;
3445import { AnalyticalTable } from './index.js' ;
@@ -625,3 +636,162 @@ export const KitchenSink: Story = {
625636 return context . viewMode === 'story' ? < AnalyticalTable { ...args } /> : < ToggleableTable { ...args } /> ;
626637 } ,
627638} ;
639+
640+ const inputCols = [
641+ {
642+ Header : 'Input' ,
643+ id : 'input' ,
644+ Cell : ( props ) => {
645+ return (
646+ < Input
647+ onFocus = { console . log }
648+ tabIndex = { props . state . cellTabIndex }
649+ inert = { Object . hasOwn ( props . state , 'tabIndex' ) ? props . state . cellTabIndex < 0 : true }
650+ />
651+ ) ;
652+ } ,
653+ interactiveElementName : 'Input' ,
654+ } ,
655+ {
656+ Header : 'Button' ,
657+ id : 'btn' ,
658+ Cell : ( props ) => (
659+ < Button tabIndex = { props . state . cellTabIndex } inert = { props . state . cellTabIndex < 0 } >
660+ Button
661+ </ Button >
662+ ) ,
663+ interactiveElementName : ( ) => 'Button' ,
664+ } ,
665+ ] ;
666+
667+ const useGetTableProps = ( props , { instance } ) => {
668+ const handleFocus : FocusEventHandler < HTMLDivElement > = ( e ) => {
669+ const isCell = e . target . hasAttribute ( 'gridcell' ) || e . target . hasAttribute ( 'columnheader' ) ;
670+ if ( isCell ) {
671+ }
672+ if ( typeof props . onFocus === 'function' ) {
673+ props . onFocus ( e ) ;
674+ }
675+ } ;
676+
677+ const handleKeyDown : KeyboardEventHandler < HTMLDivElement > = ( e ) => { } ;
678+
679+ return [ props , { onFocus : handleFocus , onKeyDown : handleKeyDown } ] ;
680+ } ;
681+
682+ function findFirstFocusableInside ( element ) {
683+ if ( ! element ) return null ;
684+
685+ function recursiveFindInteractiveElement ( el ) {
686+ for ( const child of el . children ) {
687+ const style = getComputedStyle ( child ) ;
688+ if ( child . disabled || style . display === 'none' || style . visibility === 'hidden' ) {
689+ continue ; // skip non-interactive
690+ }
691+
692+ const focusableSelectors = [
693+ 'a[href]' ,
694+ 'button' ,
695+ 'input' ,
696+ 'textarea' ,
697+ 'select' ,
698+ '[tabindex]:not([tabindex="-1"])' ,
699+ ] ;
700+
701+ if ( child . matches ( focusableSelectors . join ( ',' ) ) ) {
702+ return child ;
703+ }
704+
705+ if ( child . shadowRoot ) {
706+ const shadowFocusable = recursiveFindInteractiveElement ( child . shadowRoot ) ;
707+ if ( shadowFocusable ) return shadowFocusable ;
708+ }
709+
710+ const nestedFocusable = recursiveFindInteractiveElement ( child ) ;
711+ if ( nestedFocusable ) return nestedFocusable ;
712+ }
713+ return null ;
714+ }
715+
716+ return recursiveFindInteractiveElement ( element ) ;
717+ }
718+
719+ const useCellTabIndex = ( cols , { instance : { state } } ) => {
720+ console . log ( state . cellTabIndex ) ;
721+ return cols . map ( ( col ) => {
722+ const origCell = col . Cell ;
723+
724+ // only wrap function renderers, non-function renderers don't receive props anyway.
725+ if ( typeof origCell !== 'function' ) return col ;
726+
727+ return {
728+ ...col ,
729+ Cell : ( props : any ) => {
730+ return origCell ( { ...props , tabIndex : state . cellTabIndex ?? - 1 } ) ;
731+ } ,
732+ } ;
733+ } ) ;
734+ } ;
735+
736+ const useGetCellProps = ( props , { cell, instance, userProps } ) => {
737+ const { interactiveElementName, Cell } = cell . column ;
738+
739+ const handleKeyDown : KeyboardEventHandler < HTMLDivElement > = ( e ) => {
740+ if ( e . key === 'F2' ) {
741+ if ( e . currentTarget === e . target && interactiveElementName ) {
742+ let name : string ;
743+ const interactiveElement = findFirstFocusableInside ( e . target ) ;
744+ if ( interactiveElement ) {
745+ e . currentTarget . tabIndex = - 1 ;
746+ interactiveElement . focus ( ) ;
747+ instance . dispatch ( { type : 'CELL_TAB_INDEX' , payload : 0 } ) ;
748+ if ( typeof interactiveElementName === 'function' ) {
749+ name = interactiveElementName ( cell ) ;
750+ } else {
751+ name = interactiveElementName ;
752+ }
753+ }
754+ }
755+ if ( e . currentTarget !== e . target ) {
756+ e . currentTarget . tabIndex = 0 ;
757+ e . currentTarget . focus ( ) ;
758+ instance . dispatch ( { type : 'CELL_TAB_INDEX' , payload : - 1 } ) ;
759+ }
760+ }
761+ } ;
762+
763+ return [ props , { onKeyDown : handleKeyDown } ] ;
764+ } ;
765+
766+ const stateReducer = ( state , action , _prevState , instance : TableInstance ) => {
767+ const { payload, type } = action ;
768+
769+ if ( type === 'CELL_TAB_INDEX' ) {
770+ return { ...state , cellTabIndex : payload } ;
771+ }
772+ return state ;
773+ } ;
774+
775+ const useF2Navigation = ( hooks : ReactTableHooks ) => {
776+ // const prevFocusedCell = useRef<HTMLDivElement>(null);
777+ //todo: param names claim functions are hooks, but they aren't
778+ hooks . visibleColumns . push ( useCellTabIndex ) ;
779+ hooks . getCellProps . push ( useGetCellProps ) ;
780+ hooks . stateReducers . push ( stateReducer ) ;
781+ // hooks.getTableProps.push(useGetTableProps);
782+ // hooks.getHeaderProps.push(setHeaderProps);
783+ } ;
784+
785+ const tableHooks = [ useF2Navigation ] ;
786+
787+ export const Test : Story = {
788+ render ( args ) {
789+ return (
790+ < >
791+ < button > Click</ button >
792+ < AnalyticalTable { ...args } columns = { [ inputCols [ 0 ] , ...args . columns , inputCols [ 1 ] ] } tableHooks = { tableHooks } />
793+ < button > Click</ button >
794+ </ >
795+ ) ;
796+ } ,
797+ } ;
0 commit comments