Skip to content

Commit 96baa0a

Browse files
committed
wip
1 parent 6b3d932 commit 96baa0a

File tree

1 file changed

+171
-1
lines changed

1 file changed

+171
-1
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.stories.tsx

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
1+
import type { ReactTableHooks, TableInstance } from '@/components/AnalyticalTable/types/index.js';
12
import dataLarge from '@sb/mockData/Friends500.json';
23
import dataTree from '@sb/mockData/FriendsTree.json';
34
import type { Meta, StoryObj } from '@storybook/react-vite';
45
import '@ui5/webcomponents-icons/dist/delete.js';
56
import '@ui5/webcomponents-icons/dist/edit.js';
67
import '@ui5/webcomponents-icons/dist/settings.js';
78
import 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';
919
import {
1020
AnalyticalTablePopinDisplay,
1121
AnalyticalTableScaleWidthMode,
@@ -29,6 +39,7 @@ import { SegmentedButtonItem } from '../../webComponents/SegmentedButtonItem/ind
2939
import { Select } from '../../webComponents/Select/index.js';
3040
import { Tag } from '../../webComponents/Tag/index.js';
3141
import { Text } from '../../webComponents/Text/index.js';
42+
import { Input } from '../../webComponents/input/index.js';
3243
import { FlexBox } from '../FlexBox/index.js';
3344
import type { AnalyticalTableColumnDefinition } from './index.js';
3445
import { 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

Comments
 (0)