UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

98 lines 4.8 kB
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