@furystack/shades-common-components
Version:
Common UI components for FuryStack Shades
98 lines • 4.8 kB
JavaScript
import { createComponent, Shade } from '@furystack/shades';
import { cssVariableTheme } from '../../services/css-variable-theme.js';
export const ListItem = Shade({
customElementName: 'shade-list-item',
css: {
display: 'flex',
fontFamily: cssVariableTheme.typography.fontFamily,
alignItems: 'center',
cursor: 'default',
userSelect: 'none',
padding: `${cssVariableTheme.spacing.sm} ${cssVariableTheme.spacing.md}`,
gap: cssVariableTheme.spacing.sm,
transition: `background-color ${cssVariableTheme.transitions.duration.fast} ease, box-shadow ${cssVariableTheme.transitions.duration.fast} ${cssVariableTheme.transitions.easing.easeInOut}`,
borderLeft: '3px solid transparent',
'&:not([data-selected]):hover': {
backgroundColor: cssVariableTheme.action.hoverBackground,
},
'&[data-selected]': {
backgroundColor: cssVariableTheme.action.selectedBackground,
borderLeft: `3px solid ${cssVariableTheme.palette.primary.main}`,
},
'&[data-focused]': {
boxShadow: `0 0 0 2px ${cssVariableTheme.palette.primary.main} inset`,
},
},
render: ({ props, useObservable, useHostProps, useRef }) => {
const { item, listService, renderItem, renderIcon, renderSecondaryActions, onActivate } = props;
const [selection] = useObservable('selection', listService.selection);
const [focusedItem] = useObservable('focusedItem', listService.focusedItem);
const isFocused = focusedItem === item;
const isSelected = selection.includes(item);
useHostProps({
tabIndex: isFocused ? 0 : -1,
'data-spatial-nav-target': '',
role: 'option',
'aria-selected': isSelected.toString(),
onpointerdown: () => {
listService.setFocusAnchor();
},
onfocus: () => {
if (listService.focusedItem.getValue() !== item) {
listService.focusedItem.setValue(item);
}
if (!listService.hasFocus.getValue()) {
listService.hasFocus.setValue(true);
}
},
onclick: (ev) => {
listService.handleItemClick(item, ev);
},
ondblclick: () => {
listService.handleItemDoubleClick(item);
onActivate?.(item);
},
...(isSelected ? { 'data-selected': '' } : {}),
...(isFocused ? { 'data-focused': '' } : {}),
});
const wrapperRef = useRef('wrapper');
if (isFocused) {
queueMicrotask(() => {
const el = wrapperRef.current;
if (!el)
return;
const hostEl = el.closest('shade-list-item');
if (!hostEl)
return;
if (document.activeElement !== hostEl) {
hostEl.focus({ preventScroll: true });
}
const scrollContainer = el.closest('shade-list');
if (scrollContainer) {
const containerRect = scrollContainer.getBoundingClientRect();
const itemRect = hostEl.getBoundingClientRect();
const itemTopInContainer = itemRect.top - containerRect.top;
const itemBottomInContainer = itemRect.bottom - containerRect.top;
if (itemTopInContainer < 0) {
scrollContainer.scrollTo({
top: scrollContainer.scrollTop + itemTopInContainer,
behavior: 'instant',
});
}
else if (itemBottomInContainer > scrollContainer.clientHeight) {
scrollContainer.scrollTo({
top: scrollContainer.scrollTop + (itemBottomInContainer - scrollContainer.clientHeight),
behavior: 'instant',
});
}
}
});
}
const state = { isFocused, isSelected };
return (createComponent("span", { ref: wrapperRef, style: { display: 'contents' } },
renderIcon && createComponent("span", { className: "list-item-icon" }, renderIcon(item)),
createComponent("span", { className: "list-item-content", style: { flex: '1' } }, renderItem(item, state)),
renderSecondaryActions && (createComponent("span", { className: "list-item-actions", style: { display: 'flex', gap: cssVariableTheme.spacing.xs } }, renderSecondaryActions(item).map((action) => action)))));
},
});
//# sourceMappingURL=list-item.js.map