feat: Enhance keyboard navigation and accessibility in the dashboard
- Added a `disabled` prop to `SplitPaneSecondaryProps` for better control over split view behavior. - Implemented a context for managing keyboard navigation settings in the `Tabs` component, allowing for disabling keyboard navigation. - Updated `TabsTrigger` to conditionally handle keyboard events based on the context. - Introduced navigation order building and item navigation functions to streamline board and clip navigation. - Enhanced clipboard history page with keyboard navigation support, allowing users to navigate between clips and boards using arrow keys and tab. - Refactored signal store to include navigation context and selected item signals for improved state management. - Updated UI components to reflect keyboard selection states and improve accessibility. - Added new translations for layout options in the dashboard.
This commit is contained in:
parent
2144265b9c
commit
803fdf5c97
@ -49,6 +49,9 @@ npm run format
|
||||
|
||||
# Version management
|
||||
npm run version:sync
|
||||
|
||||
# Translation audit
|
||||
npm run translation-audit
|
||||
```
|
||||
|
||||
### Frontend Development (packages/pastebar-app-ui/)
|
||||
|
@ -54,4 +54,5 @@ export type SplitPanePrimaryProps = { children: ReactNode } & DOMProps & {
|
||||
export type SplitPaneSecondaryProps = { children: ReactNode } & DOMProps & {
|
||||
isSplitPanelView?: boolean
|
||||
isFullWidth?: boolean
|
||||
disabled?: boolean // Added disabled prop
|
||||
}
|
||||
|
@ -3,12 +3,21 @@ import * as TabsPrimitive from '@radix-ui/react-tabs'
|
||||
|
||||
import { cn } from '~/lib/utils'
|
||||
|
||||
// Create a context to share the setting from TabsList to TabsTrigger
|
||||
const TabsContext = React.createContext({
|
||||
disableKeyboardNavigation: false,
|
||||
})
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> & {
|
||||
disableKeyboardNavigation?: boolean
|
||||
}
|
||||
>(({ className, children, disableKeyboardNavigation = false, ...props }, ref) => (
|
||||
// The provider makes the 'disableKeyboardNavigation' value available to all child components
|
||||
<TabsContext.Provider value={{ disableKeyboardNavigation }}>
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
@ -16,23 +25,45 @@ const TabsList = React.forwardRef<
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</TabsPrimitive.List>
|
||||
</TabsContext.Provider>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
>(({ className, onKeyDown, ...props }, ref) => {
|
||||
// Consume the context to get the setting from the parent TabsList
|
||||
const { disableKeyboardNavigation } = React.useContext(TabsContext)
|
||||
|
||||
// Create a new keydown handler
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
// If the feature is enabled, prevent arrow key navigation (with or without modifiers)
|
||||
if (disableKeyboardNavigation && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation() // Also stop propagation to prevent other handlers from running
|
||||
}
|
||||
|
||||
// IMPORTANT: Still call any original onKeyDown function that was passed in props
|
||||
onKeyDown?.(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all data-[state=active]:bg-background dark:data-[state=active]:bg-gray-600 data-[state=active]:text-foreground data-[state=active]:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset disabled:pointer-events-none disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
// Conditionally apply our new handler ONLY if the prop is set
|
||||
onKeyDown={disableKeyboardNavigation ? handleKeyDown : onKeyDown}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
)
|
||||
})
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
|
@ -1,9 +1,25 @@
|
||||
import { UniqueIdentifier } from '@dnd-kit/core'
|
||||
import createBoardTree from '~/libs/create-board-tree'
|
||||
import { clsx, type ClassValue } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import {
|
||||
currentBoardIndex,
|
||||
currentNavigationContext,
|
||||
keyboardSelectedBoardId,
|
||||
keyboardSelectedClipId,
|
||||
} from '~/store/signalStore'
|
||||
|
||||
import { MenuItem } from '~/types/menu'
|
||||
|
||||
// Navigation types and helper functions
|
||||
interface NavigationItem {
|
||||
id: UniqueIdentifier
|
||||
type: 'history' | 'board'
|
||||
parentId?: UniqueIdentifier | null
|
||||
depth: number
|
||||
}
|
||||
|
||||
const EMOJIREGEX =
|
||||
/(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/gm
|
||||
|
||||
@ -54,6 +70,231 @@ export function absoluteUrl(path: string) {
|
||||
return `${process.env.NEXT_PUBLIC_APP_URL}${path}`
|
||||
}
|
||||
|
||||
// Build a flattened navigation order that includes boards and sub-boards
|
||||
// Only boards are included in Left/Right navigation - clips are navigated with Up/Down within boards
|
||||
export function buildNavigationOrder(
|
||||
clipItems: any[],
|
||||
currentTab: string
|
||||
): NavigationItem[] {
|
||||
const navigationOrder: NavigationItem[] = []
|
||||
|
||||
// Add history placeholder - actual history navigation is handled by ClipboardHistoryPage
|
||||
navigationOrder.push({ id: 'history', type: 'history', depth: 0 })
|
||||
|
||||
// Get all boards and clips in the current tab
|
||||
const allItems = clipItems.filter(item => item.tabId === currentTab)
|
||||
|
||||
// Build board tree for proper nesting
|
||||
const boardTree = createBoardTree(clipItems, currentTab, null)
|
||||
|
||||
// Recursively add boards (but not clips - clips are navigated with Up/Down within boards)
|
||||
function addBoardAndContents(boardId: UniqueIdentifier, depth: number) {
|
||||
const board = allItems.find(item => item.itemId === boardId && item.isBoard)
|
||||
if (!board) return
|
||||
|
||||
// Add the board itself to navigation order
|
||||
navigationOrder.push({
|
||||
id: boardId,
|
||||
type: 'board',
|
||||
parentId: board.parentId,
|
||||
depth,
|
||||
})
|
||||
|
||||
// Get direct child boards (not clips) sorted by order
|
||||
const childBoards = allItems
|
||||
.filter(item => item.parentId === boardId && item.isBoard)
|
||||
.sort((a, b) => a.orderNumber - b.orderNumber)
|
||||
|
||||
// Add child boards recursively
|
||||
childBoards.forEach(child => {
|
||||
addBoardAndContents(child.itemId, depth + 1)
|
||||
})
|
||||
}
|
||||
|
||||
// Start with top-level boards
|
||||
const topLevelBoards = allItems
|
||||
.filter(item => item.isBoard && item.parentId === null)
|
||||
.sort((a, b) => a.orderNumber - b.orderNumber)
|
||||
|
||||
topLevelBoards.forEach(board => {
|
||||
addBoardAndContents(board.itemId, 1)
|
||||
})
|
||||
|
||||
return navigationOrder
|
||||
}
|
||||
|
||||
// Find current position in navigation order
|
||||
export function findCurrentNavigationIndex(navigationOrder: NavigationItem[]): number {
|
||||
if (currentNavigationContext.value === 'history') {
|
||||
return 0 // History is always at index 0
|
||||
} else if (currentNavigationContext.value === 'board') {
|
||||
if (keyboardSelectedBoardId.value) {
|
||||
// Find board position - since clips are not in navigation order,
|
||||
// we find the board that contains the selected clip
|
||||
return navigationOrder.findIndex(
|
||||
item => item.type === 'board' && item.id === keyboardSelectedBoardId.value
|
||||
)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Find the next non-empty board in the navigation order (skipping empty boards)
|
||||
function findNextNonEmptyBoard(
|
||||
navigationOrder: NavigationItem[],
|
||||
startIndex: number,
|
||||
direction: 'forward' | 'backward',
|
||||
clipItems: any[],
|
||||
currentTab: string
|
||||
): NavigationItem | null {
|
||||
const maxAttempts = navigationOrder.length // Prevent infinite loops
|
||||
let attempts = 0
|
||||
let currentIndex = startIndex
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
// Move in the specified direction
|
||||
if (direction === 'forward') {
|
||||
currentIndex = currentIndex + 1
|
||||
// If we've gone past the end, wrap to history (index 0) or return null for no more boards
|
||||
if (currentIndex >= navigationOrder.length) {
|
||||
return null // Let caller handle going back to history
|
||||
}
|
||||
} else {
|
||||
currentIndex = currentIndex - 1
|
||||
// If we've gone before the beginning, return null for going to history
|
||||
if (currentIndex < 1) {
|
||||
// Index 0 is history, so < 1 means we should go to history
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const candidateItem = navigationOrder[currentIndex]
|
||||
|
||||
// Skip history item (only boards should be checked)
|
||||
if (candidateItem.type === 'history') {
|
||||
attempts++
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this board has clips
|
||||
const clipsInBoard = clipItems.filter(
|
||||
clipItem =>
|
||||
clipItem.isClip &&
|
||||
clipItem.parentId === candidateItem.id &&
|
||||
clipItem.tabId === currentTab
|
||||
)
|
||||
|
||||
// If board has clips, return it
|
||||
if (clipsInBoard.length > 0) {
|
||||
return candidateItem
|
||||
}
|
||||
|
||||
attempts++
|
||||
}
|
||||
|
||||
// If no non-empty board found, return null
|
||||
return null
|
||||
}
|
||||
|
||||
// Navigate to specific item in the navigation order
|
||||
export function navigateToItem(
|
||||
item: NavigationItem,
|
||||
clipItems: any[],
|
||||
currentTab: string
|
||||
) {
|
||||
if (item.type === 'history') {
|
||||
currentNavigationContext.value = 'history'
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
// Let ClipboardHistoryPage handle history item selection
|
||||
} else if (item.type === 'board') {
|
||||
// Check if the target board has clips
|
||||
const clipsInBoard = clipItems
|
||||
.filter(
|
||||
clipItem =>
|
||||
clipItem.isClip &&
|
||||
clipItem.parentId === item.id &&
|
||||
clipItem.tabId === currentTab
|
||||
)
|
||||
.sort((a, b) => a.orderNumber - b.orderNumber)
|
||||
|
||||
// If the board is empty, try to find a non-empty board
|
||||
if (clipsInBoard.length === 0) {
|
||||
// Build navigation order to find alternatives
|
||||
const navigationOrder = buildNavigationOrder(clipItems, currentTab)
|
||||
const currentIndex = navigationOrder.findIndex(navItem => navItem.id === item.id)
|
||||
|
||||
// Try to find next non-empty board in forward direction first
|
||||
let alternativeBoard = findNextNonEmptyBoard(
|
||||
navigationOrder,
|
||||
currentIndex,
|
||||
'forward',
|
||||
clipItems,
|
||||
currentTab
|
||||
)
|
||||
|
||||
// If no forward board found, try backward direction
|
||||
if (!alternativeBoard) {
|
||||
alternativeBoard = findNextNonEmptyBoard(
|
||||
navigationOrder,
|
||||
currentIndex,
|
||||
'backward',
|
||||
clipItems,
|
||||
currentTab
|
||||
)
|
||||
}
|
||||
|
||||
// If we found an alternative non-empty board, navigate to it instead
|
||||
if (alternativeBoard) {
|
||||
navigateToItem(alternativeBoard, clipItems, currentTab)
|
||||
return
|
||||
}
|
||||
|
||||
// If no non-empty boards found anywhere, go to history
|
||||
currentNavigationContext.value = 'history'
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
// Board has clips, proceed with normal navigation
|
||||
currentNavigationContext.value = 'board'
|
||||
keyboardSelectedBoardId.value = item.id
|
||||
keyboardSelectedClipId.value = clipsInBoard[0].itemId // Select first clip
|
||||
|
||||
// Update board index - for subboards, find the root parent
|
||||
const topLevelBoards = clipItems
|
||||
.filter(
|
||||
boardItem =>
|
||||
boardItem.isBoard &&
|
||||
boardItem.parentId === null &&
|
||||
boardItem.tabId === currentTab
|
||||
)
|
||||
.sort((a, b) => a.orderNumber - b.orderNumber)
|
||||
|
||||
// Find the root parent board for subboards
|
||||
const currentBoard = clipItems.find(boardItem => boardItem.itemId === item.id)
|
||||
if (currentBoard) {
|
||||
let rootParent = currentBoard
|
||||
while (rootParent.parentId !== null) {
|
||||
rootParent = clipItems.find(boardItem => boardItem.itemId === rootParent.parentId)
|
||||
if (!rootParent) break
|
||||
}
|
||||
if (rootParent) {
|
||||
currentBoardIndex.value = topLevelBoards.findIndex(
|
||||
board => board.itemId === rootParent.itemId
|
||||
)
|
||||
} else {
|
||||
currentBoardIndex.value = topLevelBoards.findIndex(
|
||||
board => board.itemId === item.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const findNewChildrenOrderByParentIdAndDragId = (
|
||||
items: MenuItem[],
|
||||
dragId: string,
|
||||
@ -305,19 +546,16 @@ export function bgColor(
|
||||
) {
|
||||
const colorNameToUse = colorName || 'slate'
|
||||
|
||||
const darkColorCode = darkCode
|
||||
? darkCode
|
||||
: colorCode === '200' && colorNameToUse === 'slate'
|
||||
? '700'
|
||||
: colorNameToUse !== 'slate'
|
||||
? '900'
|
||||
: '300'
|
||||
? '600'
|
||||
: '400'
|
||||
? '500'
|
||||
: '600'
|
||||
? '700'
|
||||
: '300'
|
||||
let darkColorCode: string
|
||||
if (darkCode) {
|
||||
darkColorCode = darkCode
|
||||
} else if (colorCode === '200' && colorNameToUse === 'slate') {
|
||||
darkColorCode = '700'
|
||||
} else if (colorNameToUse !== 'slate') {
|
||||
darkColorCode = '900'
|
||||
} else {
|
||||
darkColorCode = '300'
|
||||
}
|
||||
|
||||
const type = isBorder ? 'border' : 'bg'
|
||||
|
||||
|
@ -147,8 +147,10 @@ Filter's Value: Filter's Value
|
||||
Find in Clip: Find in Clip
|
||||
Find in clip: Find in clip
|
||||
Find in history: Find in history
|
||||
Flex Layout: Flex Layout
|
||||
Form Fields: Form Fields
|
||||
General Fields: General Fields
|
||||
Grid Layout: Grid Layout
|
||||
HTML: HTML
|
||||
Headers: Headers
|
||||
Hide Label: Hide Label
|
||||
|
@ -234,6 +234,7 @@ export function ClipboardHistoryRowComponent({
|
||||
rowKeyboardRef.current.scrollIntoView({
|
||||
block: 'center',
|
||||
})
|
||||
// rowKeyboardRef.current.focus()
|
||||
}
|
||||
}, [isKeyboardSelected, isScrolling])
|
||||
|
||||
@ -403,7 +404,7 @@ export function ClipboardHistoryRowComponent({
|
||||
!isSelected
|
||||
? 'bg-teal-50 hover:border-slate-300 dark:bg-sky-900/40 dark:hover:border-slate-700 hover:bg-teal-50/90 hover:dark:bg-sky-950'
|
||||
: isKeyboardSelected
|
||||
? `bg-blue-50 !shadow-sm border-blue-300 dark:bg-blue-950/80 dark:border-blue-900/80 hover:border-blue-300/80 dark:hover:border-blue-800 hover:bg-blue-50/80 ${
|
||||
? `bg-blue-50 ring-2 scale-[.98] ring-blue-400 dark:!ring-blue-600 ring-offset-1 !shadow-sm border-blue-300 dark:bg-blue-950/80 dark:hover:border-blue-800 hover:bg-blue-50/80 ring-offset-white dark:ring-offset-gray-800 ${
|
||||
isPinnedTop ? ' dark:!bg-amber-950' : ''
|
||||
}`
|
||||
: isDeleting && !isDragPreview
|
||||
|
@ -31,8 +31,13 @@ import {
|
||||
activeOverTabId,
|
||||
collectionsStoreAtom,
|
||||
createFirstBoard,
|
||||
currentBoardIndex,
|
||||
currentNavigationContext,
|
||||
isFullyExpandViewBoard,
|
||||
isKeyAltPressed,
|
||||
keyboardSelectedBoardId,
|
||||
keyboardSelectedClipId,
|
||||
keyboardSelectedItemId,
|
||||
playerStoreAtom,
|
||||
settingsStoreAtom,
|
||||
showClipsMoveOnBoardId,
|
||||
@ -57,6 +62,7 @@ import {
|
||||
Plus,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { bgColor } from '~/lib/utils'
|
||||
@ -103,6 +109,7 @@ import {
|
||||
import { useUpdateTabs } from '~/hooks/queries/use-tabs'
|
||||
import { useCopyClipItem, usePasteClipItem } from '~/hooks/use-copypaste-clip-item'
|
||||
import { useLocalStorage } from '~/hooks/use-localstorage'
|
||||
// import { useNavigation } from '~/hooks/use-navigation'
|
||||
import { useSignal } from '~/hooks/use-signal'
|
||||
|
||||
import { Item } from '~/types/menu'
|
||||
@ -255,6 +262,31 @@ function DashboardComponent({
|
||||
[clipItems]
|
||||
)
|
||||
|
||||
// // Prepare board contexts for navigation
|
||||
// const boardContexts = useMemo(() => {
|
||||
// return clipItems
|
||||
// .filter(
|
||||
// ({ parentId, isBoard, tabId }) =>
|
||||
// parentId === null && isBoard && tabId === currentTab
|
||||
// )
|
||||
// .map(board => ({
|
||||
// boardId: board.itemId.toString(),
|
||||
// boardName: board.name,
|
||||
// clips: clipItems
|
||||
// .filter(
|
||||
// ({ parentId, isClip, tabId }) =>
|
||||
// parentId === board.itemId && isClip && tabId === currentTab
|
||||
// )
|
||||
// .map(clip => ({ id: clip.itemId, itemId: clip.itemId }))
|
||||
// }))
|
||||
// }, [clipItems, currentTab])
|
||||
|
||||
// // Use unified navigation hook (empty history items for Dashboard only)
|
||||
// const { selectedClipId } = useNavigation({
|
||||
// historyItems: [],
|
||||
// boardContexts,
|
||||
// })
|
||||
|
||||
useEffect(() => {
|
||||
if (pinnedClips.length === 0) {
|
||||
isPinnedPanelHovering.value = false
|
||||
@ -907,6 +939,7 @@ function DashboardComponent({
|
||||
pinnedItemIds={pinnedClips.map(clip => clip.id)}
|
||||
currentTab={currentTab}
|
||||
setCurrentTab={setCurrentTab}
|
||||
isKeyboardNavigationDisabled={currentNavigationContext.value !== null}
|
||||
/>
|
||||
</SortableContext>
|
||||
)}
|
||||
@ -975,6 +1008,9 @@ function DashboardComponent({
|
||||
setShowDetailsItem={setShowDetailsItem}
|
||||
selectedItemIds={selectedItemIds}
|
||||
setSelectedItemId={setSelectedItemId}
|
||||
keyboardSelectedClipId={keyboardSelectedClipId}
|
||||
currentSelectedBoardId={keyboardSelectedBoardId}
|
||||
keyboardNavigationMode={currentNavigationContext}
|
||||
/>
|
||||
))}
|
||||
</PanelGroup>
|
||||
@ -1007,6 +1043,9 @@ function DashboardComponent({
|
||||
setShowDetailsItem={setShowDetailsItem}
|
||||
selectedItemIds={selectedItemIds}
|
||||
setSelectedItemId={setSelectedItemId}
|
||||
keyboardSelectedClipId={keyboardSelectedClipId}
|
||||
currentSelectedBoardId={keyboardSelectedBoardId}
|
||||
keyboardNavigationMode={currentNavigationContext}
|
||||
/>
|
||||
<Flex className="absolute right-0 w-full bottom-[-13px] z-100">
|
||||
<Button
|
||||
|
@ -152,6 +152,9 @@ interface BoardProps {
|
||||
showDetailsItem?: UniqueIdentifier | null
|
||||
setShowDetailsItem?: (id: UniqueIdentifier | null) => void
|
||||
isDragPreview?: boolean
|
||||
keyboardSelectedClipId?: { value: UniqueIdentifier | null }
|
||||
currentSelectedBoardId?: { value: UniqueIdentifier | null }
|
||||
keyboardNavigationMode?: { value: 'history' | 'board' | null }
|
||||
}
|
||||
|
||||
export function BoardComponent({
|
||||
@ -173,6 +176,9 @@ export function BoardComponent({
|
||||
setShowDetailsItem,
|
||||
setCurrentTab,
|
||||
setSelectedItemId,
|
||||
keyboardSelectedClipId,
|
||||
currentSelectedBoardId,
|
||||
keyboardNavigationMode,
|
||||
}: BoardProps) {
|
||||
const { t } = useTranslation()
|
||||
const childrenIds = useMemo(() => {
|
||||
@ -692,6 +698,9 @@ export function BoardComponent({
|
||||
setSelectedItemId={setSelectedItemId}
|
||||
showDetailsItem={showDetailsItem}
|
||||
setShowDetailsItem={setShowDetailsItem}
|
||||
keyboardSelectedClipId={keyboardSelectedClipId}
|
||||
currentSelectedBoardId={currentSelectedBoardId}
|
||||
keyboardNavigationMode={keyboardNavigationMode}
|
||||
/>
|
||||
) : (
|
||||
item.type === CLIP && (
|
||||
@ -747,6 +756,10 @@ export function BoardComponent({
|
||||
selectedOrder={
|
||||
selectedItemIds.indexOf(item.id) + 1
|
||||
}
|
||||
isKeyboardSelected={
|
||||
keyboardSelectedClipId?.value === item.id &&
|
||||
keyboardNavigationMode?.value === 'board'
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
@ -980,6 +993,9 @@ export function BoardWithPanel({
|
||||
order,
|
||||
isLastBoard,
|
||||
setSelectedItemId,
|
||||
keyboardSelectedClipId,
|
||||
currentSelectedBoardId,
|
||||
keyboardNavigationMode,
|
||||
}: BoardProps) {
|
||||
return (
|
||||
<ResizePanel
|
||||
@ -1012,6 +1028,9 @@ export function BoardWithPanel({
|
||||
setShowDetailsItem={setShowDetailsItem}
|
||||
isDark={isDark}
|
||||
isDragPreview={isDragPreview}
|
||||
keyboardSelectedClipId={keyboardSelectedClipId}
|
||||
currentSelectedBoardId={currentSelectedBoardId}
|
||||
keyboardNavigationMode={keyboardNavigationMode}
|
||||
/>
|
||||
</ResizePanel>
|
||||
)
|
||||
|
@ -100,6 +100,7 @@ export default function BoardTabs({
|
||||
pinnedItemIds,
|
||||
currentTab,
|
||||
setCurrentTab,
|
||||
isKeyboardNavigationDisabled,
|
||||
}: {
|
||||
tabs: TabsType[]
|
||||
currentTab: string
|
||||
@ -107,6 +108,7 @@ export default function BoardTabs({
|
||||
pinnedItemIds: UniqueIdentifier[]
|
||||
setSelectedItemIds: (ids: UniqueIdentifier[]) => void
|
||||
setCurrentTab: (tab: string) => void
|
||||
isKeyboardNavigationDisabled?: boolean
|
||||
}) {
|
||||
const { clipboardHistory } = useAtomValue(clipboardHistoryStoreAtom)
|
||||
const { isSimplifiedLayout } = useAtomValue(settingsStoreAtom)
|
||||
@ -370,7 +372,10 @@ export default function BoardTabs({
|
||||
>
|
||||
<SimpleBar style={{ width: '97%' }}>
|
||||
{!showEditTabs.value ? (
|
||||
<TabsList className="bg-transparent pr-0.5">
|
||||
<TabsList
|
||||
className="bg-transparent pr-0.5"
|
||||
disableKeyboardNavigation={isKeyboardNavigationDisabled}
|
||||
>
|
||||
{tabs.map(
|
||||
({ tabId, tabName, tabIsHidden, tabOrderNumber }) =>
|
||||
tabId &&
|
||||
@ -396,7 +401,10 @@ export default function BoardTabs({
|
||||
)}
|
||||
</TabsList>
|
||||
) : (
|
||||
<TabsList className="bg-transparent pr-0.5">
|
||||
<TabsList
|
||||
className="bg-transparent pr-0.5"
|
||||
disableKeyboardNavigation={isKeyboardNavigationDisabled}
|
||||
>
|
||||
{tabs.map(
|
||||
({ tabId, tabName, tabIsHidden, tabColor, tabOrderNumber }) =>
|
||||
tabId &&
|
||||
|
@ -317,6 +317,7 @@ interface ClipCardProps {
|
||||
setShowDetailsItem?: (id: UniqueIdentifier | null) => void
|
||||
setSelectedItemId?: (id: UniqueIdentifier) => void
|
||||
isDragPreview?: boolean
|
||||
isKeyboardSelected?: boolean
|
||||
}
|
||||
|
||||
export interface ClipDragData {
|
||||
@ -356,6 +357,7 @@ export function ClipCard({
|
||||
onMovePinnedUpDown = ({}) => {},
|
||||
setShowDetailsItem = () => {},
|
||||
setSelectedItemId = () => {},
|
||||
isKeyboardSelected = false,
|
||||
}: ClipCardProps) {
|
||||
const { t } = useTranslation()
|
||||
const { isNoteIconsEnabled, defaultNoteIconType } = useAtomValue(settingsStoreAtom)
|
||||
@ -386,6 +388,7 @@ export function ClipCard({
|
||||
|
||||
const contextMenuButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const contextMenuTriggerRef = useRef<HTMLDivElement>(null)
|
||||
const clipCardRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const canReorangeClips =
|
||||
(isShowOrganizeLayoutValue || canReorangeItems) && !isPinnedBoard
|
||||
@ -461,7 +464,13 @@ export function ClipCard({
|
||||
: ''
|
||||
} ` +
|
||||
`${isPinnedBoard && !isShowOrganizeLayoutValue ? 'animate-in fade-in' : ''} ` +
|
||||
`${isShowLinkedClip ? 'pulse-clip' : ''} `,
|
||||
`${isShowLinkedClip ? 'pulse-clip' : ''} ` +
|
||||
`${
|
||||
// This was already present from a previous manual edit by the user, ensuring it's correct.
|
||||
isKeyboardSelected
|
||||
? 'ring-2 outline-none scale-[.98] ring-blue-400 dark:!ring-blue-600 ring-offset-1 ring-offset-white dark:ring-offset-gray-800 dark:bg-blue-950/80 bg-blue-50'
|
||||
: ''
|
||||
} `,
|
||||
{
|
||||
variants: {
|
||||
dragging: {
|
||||
@ -542,6 +551,14 @@ export function ClipCard({
|
||||
}
|
||||
}, [showClipFindKeyPressed.value])
|
||||
|
||||
useEffect(() => {
|
||||
if (isKeyboardSelected && clipCardRef.current) {
|
||||
clipCardRef.current.focus()
|
||||
} else {
|
||||
clipCardRef.current?.blur()
|
||||
}
|
||||
}, [isKeyboardSelected])
|
||||
|
||||
const isEditing = isClipNameEditing || isClipEdit
|
||||
|
||||
const copyDisabled =
|
||||
@ -655,7 +672,11 @@ export function ClipCard({
|
||||
)
|
||||
)}
|
||||
<Card
|
||||
ref={mergeRefs(canReorangeClips || isClipEdit ? setNodeRef : null)}
|
||||
ref={mergeRefs(
|
||||
canReorangeClips || isClipEdit ? setNodeRef : null,
|
||||
clipCardRef
|
||||
)}
|
||||
tabIndex={isKeyboardSelected ? 0 : -1}
|
||||
style={
|
||||
isDragPreview
|
||||
? {
|
||||
|
@ -18,11 +18,18 @@ import { listen } from '@tauri-apps/api/event'
|
||||
import { MainContainer } from '~/layout/Layout'
|
||||
import {
|
||||
clipboardHistoryStoreAtom,
|
||||
collectionsStoreAtom,
|
||||
createClipBoardItemId,
|
||||
createClipHistoryItemIds,
|
||||
createMenuItemFromHistoryId,
|
||||
currentBoardIndex,
|
||||
currentNavigationContext,
|
||||
keyboardSelectedItemId as globalKeyboardSelectedItemId,
|
||||
hoveringHistoryRowId,
|
||||
isKeyAltPressed,
|
||||
keyboardSelectedBoardId,
|
||||
keyboardSelectedClipId,
|
||||
keyboardSelectedItemId,
|
||||
settingsStoreAtom,
|
||||
showClipsMoveOnBoardId,
|
||||
showHistoryDeleteConfirmationId,
|
||||
@ -54,6 +61,12 @@ import { VariableSizeList } from 'react-window'
|
||||
import InfiniteLoader from 'react-window-infinite-loader'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
|
||||
import {
|
||||
buildNavigationOrder,
|
||||
findCurrentNavigationIndex,
|
||||
navigateToItem,
|
||||
} from '~/lib/utils'
|
||||
|
||||
import { Tabs, TabsList, TabsTrigger } from '~/components/ui/tabs'
|
||||
import mergeRefs from '~/components/atoms/merge-refs'
|
||||
import ToolTip from '~/components/atoms/tooltip'
|
||||
@ -97,6 +110,7 @@ import {
|
||||
useUnpinAllClipboardHistory,
|
||||
} from '~/hooks/queries/use-history-items'
|
||||
import { useUpdateItemValueByHistoryId } from '~/hooks/queries/use-items'
|
||||
import { useCopyClipItem } from '~/hooks/use-copypaste-clip-item' // Added for clip copying
|
||||
import {
|
||||
useCopyPasteHistoryItem,
|
||||
usePasteHistoryItem,
|
||||
@ -166,6 +180,7 @@ const loadPrismComponents = async () => {
|
||||
|
||||
export default function ClipboardHistoryPage() {
|
||||
const [copiedItem, setCopiedItem, runSequenceCopy] = useCopyPasteHistoryItem({})
|
||||
const [, handleCopyClipItem] = useCopyClipItem({}) // Destructure to get handleCopyClipItem
|
||||
const [pastedItem, pastingCountDown, setPastedItem, runSequencePaste] =
|
||||
usePasteHistoryItem({})
|
||||
|
||||
@ -194,8 +209,8 @@ export default function ClipboardHistoryPage() {
|
||||
const [selectedHistoryItems, setSelectedHistoryItems] = useState<UniqueIdentifier[]>([])
|
||||
const [showSelectHistoryItems, setShowSelectHistoryItems] = useState(false)
|
||||
const [isDragPinnedHistory, setIsDragPinnedHistory] = useState(false)
|
||||
const keyboardSelectedItemId = useSignal<UniqueIdentifier | null>(null)
|
||||
const navigatedWithCtrl = useSignal(false)
|
||||
// Use global signal for keyboardSelectedItemId, aliased to avoid conflict if needed locally
|
||||
// const keyboardSelectedItemId = useSignal<UniqueIdentifier | null>(null); // Removed local signal
|
||||
const {
|
||||
isScrolling,
|
||||
setIsScrolling,
|
||||
@ -228,12 +243,13 @@ export default function ClipboardHistoryPage() {
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { clipItems, currentTab } = useAtomValue(collectionsStoreAtom)
|
||||
|
||||
const { themeDark } = useAtomValue(themeStoreAtom)
|
||||
const { ref: pinnedPanelRef, height: pinnedPanelHeight } = useResizeObserver()
|
||||
|
||||
const isPinnedPanelHovering = useSignal(false)
|
||||
const isPinnedPanelKeepOpen = useSignal(false)
|
||||
const isCtrlReleased = useSignal(false)
|
||||
|
||||
const { showConfirmation, hoveringHistoryIdDelete } = useDeleteConfirmationTimer({
|
||||
hoveringHistoryRowId,
|
||||
@ -390,64 +406,292 @@ export default function ClipboardHistoryPage() {
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
['ctrl+enter', 'meta+enter'],
|
||||
e => {
|
||||
['enter'],
|
||||
async e => {
|
||||
e.preventDefault()
|
||||
const itemToCopy = keyboardSelectedItemId.value
|
||||
? keyboardSelectedItemId.value
|
||||
: clipboardHistory[0]?.historyId
|
||||
if (itemToCopy) {
|
||||
setCopiedItem(itemToCopy)
|
||||
if (currentNavigationContext.value === 'board' && keyboardSelectedClipId.value) {
|
||||
try {
|
||||
currentNavigationContext.value = null
|
||||
globalKeyboardSelectedItemId.value = null
|
||||
keyboardSelectedBoardId.value = null
|
||||
await handleCopyClipItem(keyboardSelectedClipId.value)
|
||||
keyboardSelectedClipId.value = null
|
||||
} catch (error) {
|
||||
console.error('Failed to copy clip item from hotkey', error)
|
||||
}
|
||||
|
||||
currentNavigationContext.value = null
|
||||
globalKeyboardSelectedItemId.value = null
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
} else if (
|
||||
(currentNavigationContext.value === 'history' ||
|
||||
currentNavigationContext.value === null) &&
|
||||
globalKeyboardSelectedItemId.value
|
||||
) {
|
||||
setCopiedItem(globalKeyboardSelectedItemId.value)
|
||||
} else if (
|
||||
(currentNavigationContext.value === 'history' ||
|
||||
currentNavigationContext.value === null) &&
|
||||
clipboardHistory.length > 0
|
||||
) {
|
||||
setCopiedItem(clipboardHistory[0]?.historyId)
|
||||
}
|
||||
currentNavigationContext.value = null
|
||||
globalKeyboardSelectedItemId.value = null
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
},
|
||||
{ enableOnFormTags: ['input'] }
|
||||
)
|
||||
|
||||
const currentNavigationContextValue = useMemo(
|
||||
() => currentNavigationContext.value,
|
||||
[currentNavigationContext.value, currentTab]
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
['ctrl+arrowdown', 'meta+arrowdown'],
|
||||
['arrowdown'],
|
||||
e => {
|
||||
e.preventDefault()
|
||||
|
||||
if (keyboardSelectedBoardId.value) {
|
||||
const clipsOnBoard = clipItems
|
||||
.filter(
|
||||
item =>
|
||||
item.isClip &&
|
||||
item.parentId === keyboardSelectedBoardId.value &&
|
||||
item.tabId === currentTab
|
||||
)
|
||||
.sort((a, b) => a.orderNumber - b.orderNumber)
|
||||
|
||||
if (clipsOnBoard.length === 0) return
|
||||
|
||||
let currentIndex = clipsOnBoard.findIndex(
|
||||
clip => clip.itemId === keyboardSelectedClipId.value
|
||||
)
|
||||
if (currentIndex === -1 && clipsOnBoard.length > 0) {
|
||||
keyboardSelectedClipId.value = clipsOnBoard[0].itemId
|
||||
} else {
|
||||
currentIndex = (currentIndex + 1) % clipsOnBoard.length
|
||||
keyboardSelectedClipId.value = clipsOnBoard[currentIndex].itemId
|
||||
}
|
||||
}
|
||||
},
|
||||
{ enabled: currentNavigationContextValue === 'board', enableOnFormTags: ['input'] }
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
['arrowup'],
|
||||
e => {
|
||||
e.preventDefault()
|
||||
|
||||
if (keyboardSelectedBoardId.value) {
|
||||
const clipsOnBoard = clipItems
|
||||
.filter(
|
||||
item =>
|
||||
item.isClip &&
|
||||
item.parentId === keyboardSelectedBoardId.value &&
|
||||
item.tabId === currentTab
|
||||
)
|
||||
.sort((a, b) => a.orderNumber - b.orderNumber)
|
||||
|
||||
if (clipsOnBoard.length === 0) return
|
||||
|
||||
let currentIndex = clipsOnBoard.findIndex(
|
||||
clip => clip.itemId === keyboardSelectedClipId.value
|
||||
)
|
||||
if (currentIndex === -1 && clipsOnBoard.length > 0) {
|
||||
keyboardSelectedClipId.value = clipsOnBoard[clipsOnBoard.length - 1].itemId
|
||||
} else {
|
||||
currentIndex = (currentIndex - 1 + clipsOnBoard.length) % clipsOnBoard.length
|
||||
keyboardSelectedClipId.value = clipsOnBoard[currentIndex].itemId
|
||||
}
|
||||
}
|
||||
},
|
||||
{ enabled: currentNavigationContextValue === 'board', enableOnFormTags: ['input'] }
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
['tab'],
|
||||
e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (
|
||||
currentNavigationContextValue === 'history' ||
|
||||
currentNavigationContextValue === null
|
||||
) {
|
||||
currentNavigationContext.value = 'board'
|
||||
keyboardSelectedItemId.value = null
|
||||
|
||||
const navigationOrder = buildNavigationOrder(clipItems, currentTab)
|
||||
if (navigationOrder.length > 1) {
|
||||
const firstBoardItem = navigationOrder[1]
|
||||
navigateToItem(firstBoardItem, clipItems, currentTab)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (currentNavigationContextValue === 'board') {
|
||||
const navigationOrder = buildNavigationOrder(clipItems, currentTab)
|
||||
if (navigationOrder.length <= 1) return
|
||||
|
||||
const currentIndex = findCurrentNavigationIndex(navigationOrder)
|
||||
|
||||
if (currentIndex === navigationOrder.length - 1) {
|
||||
currentNavigationContext.value = 'history'
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
const nextIndex = currentIndex + 1
|
||||
const nextItem = navigationOrder[nextIndex]
|
||||
|
||||
navigateToItem(nextItem, clipItems, currentTab)
|
||||
}
|
||||
},
|
||||
{
|
||||
enabled: true, // Always enabled, we check context inside the handler
|
||||
enableOnFormTags: ['input'],
|
||||
}
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
['shift+tab'],
|
||||
e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (
|
||||
currentNavigationContextValue === 'history' ||
|
||||
currentNavigationContextValue === null
|
||||
) {
|
||||
currentNavigationContext.value = 'board'
|
||||
keyboardSelectedItemId.value = null
|
||||
|
||||
const navigationOrder = buildNavigationOrder(clipItems, currentTab)
|
||||
if (navigationOrder.length > 1) {
|
||||
const firstBoardItem = navigationOrder[1]
|
||||
navigateToItem(firstBoardItem, clipItems, currentTab)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (currentNavigationContextValue === 'board') {
|
||||
const navigationOrder = buildNavigationOrder(clipItems, currentTab)
|
||||
if (navigationOrder.length <= 1) return
|
||||
|
||||
const currentIndex = findCurrentNavigationIndex(navigationOrder)
|
||||
|
||||
if (currentIndex === navigationOrder.length + 1) {
|
||||
currentNavigationContext.value = 'history'
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
const nextIndex = currentIndex - 1
|
||||
const nextItem = navigationOrder[nextIndex]
|
||||
|
||||
navigateToItem(nextItem, clipItems, currentTab)
|
||||
}
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
enableOnFormTags: ['input'],
|
||||
}
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
currentNavigationContext.value = null
|
||||
globalKeyboardSelectedItemId.value = null
|
||||
keyboardSelectedItemId.value = null
|
||||
hoveringHistoryRowId.value = null
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
},
|
||||
{
|
||||
enableOnFormTags: ['input'],
|
||||
}
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
['arrowdown'],
|
||||
e => {
|
||||
e.preventDefault()
|
||||
const currentItemIndex = clipboardHistory.findIndex(
|
||||
item => item.historyId === keyboardSelectedItemId.value
|
||||
item => item.historyId === globalKeyboardSelectedItemId.value
|
||||
)
|
||||
const nextItem = clipboardHistory[currentItemIndex + 1]
|
||||
if (nextItem) {
|
||||
keyboardSelectedItemId.value = nextItem.historyId
|
||||
globalKeyboardSelectedItemId.value = nextItem.historyId
|
||||
}
|
||||
},
|
||||
{ enableOnFormTags: ['input'] }
|
||||
{
|
||||
enableOnFormTags: ['input'],
|
||||
enabled:
|
||||
currentNavigationContext.value === 'history' ||
|
||||
currentNavigationContext.value === null,
|
||||
}
|
||||
)
|
||||
|
||||
useHotkeys(
|
||||
['ctrl+arrowup', 'meta+arrowup'],
|
||||
['arrowup'],
|
||||
e => {
|
||||
e.preventDefault()
|
||||
if (
|
||||
currentNavigationContext.value === 'history' ||
|
||||
currentNavigationContext.value === null
|
||||
) {
|
||||
const currentItemIndex = clipboardHistory.findIndex(
|
||||
item => item.historyId === keyboardSelectedItemId.value
|
||||
item => item.historyId === globalKeyboardSelectedItemId.value
|
||||
)
|
||||
const prevItem = clipboardHistory[currentItemIndex - 1]
|
||||
if (prevItem) {
|
||||
keyboardSelectedItemId.value = prevItem.historyId
|
||||
globalKeyboardSelectedItemId.value = prevItem.historyId
|
||||
}
|
||||
}
|
||||
},
|
||||
{ enableOnFormTags: ['input'] }
|
||||
{
|
||||
enableOnFormTags: ['input'],
|
||||
enabled:
|
||||
currentNavigationContext.value === 'history' ||
|
||||
currentNavigationContext.value === null,
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Control' || e.key === 'Meta') {
|
||||
if (isCtrlReleased.value) {
|
||||
keyboardSelectedItemId.value = clipboardHistory[0]?.historyId
|
||||
isCtrlReleased.value = false
|
||||
if (
|
||||
currentNavigationContext.value === null ||
|
||||
currentNavigationContext.value === 'history'
|
||||
) {
|
||||
currentNavigationContext.value = 'history'
|
||||
if (!globalKeyboardSelectedItemId.value && clipboardHistory.length > 0) {
|
||||
globalKeyboardSelectedItemId.value = clipboardHistory[0]?.historyId
|
||||
}
|
||||
} else if (currentNavigationContext.value === 'board') {
|
||||
globalKeyboardSelectedItemId.value = null // Ensure no history item is selected
|
||||
}
|
||||
navigatedWithCtrl.value = true
|
||||
}
|
||||
}
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Control' || e.key === 'Meta') {
|
||||
isCtrlReleased.value = true
|
||||
keyboardSelectedItemId.value = null
|
||||
navigatedWithCtrl.value = false
|
||||
// Reset all navigation states
|
||||
currentNavigationContext.value = null
|
||||
globalKeyboardSelectedItemId.value = null
|
||||
keyboardSelectedBoardId.value = null
|
||||
keyboardSelectedClipId.value = null
|
||||
currentBoardIndex.value = 0
|
||||
hoveringHistoryRowId.value = null
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
@ -456,13 +700,45 @@ export default function ClipboardHistoryPage() {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
}
|
||||
}, [clipboardHistory])
|
||||
}, [
|
||||
clipboardHistory,
|
||||
currentNavigationContext,
|
||||
globalKeyboardSelectedItemId,
|
||||
keyboardSelectedBoardId,
|
||||
keyboardSelectedClipId,
|
||||
currentBoardIndex,
|
||||
]) // Added dependencies
|
||||
|
||||
useEffect(() => {
|
||||
if (keyboardSelectedItemId.value) {
|
||||
hoveringHistoryRowId.value = keyboardSelectedItemId.value
|
||||
if (
|
||||
currentNavigationContext.value === 'history' &&
|
||||
!globalKeyboardSelectedItemId.value &&
|
||||
clipboardHistory.length > 0
|
||||
) {
|
||||
globalKeyboardSelectedItemId.value = clipboardHistory[0]?.historyId
|
||||
}
|
||||
}, [keyboardSelectedItemId.value])
|
||||
}, [
|
||||
currentNavigationContext.value,
|
||||
globalKeyboardSelectedItemId.value,
|
||||
clipboardHistory,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
globalKeyboardSelectedItemId.value &&
|
||||
(currentNavigationContext.value === 'history' ||
|
||||
currentNavigationContext.value === null)
|
||||
) {
|
||||
hoveringHistoryRowId.value = globalKeyboardSelectedItemId.value
|
||||
} else if (!globalKeyboardSelectedItemId.value && !keyboardSelectedClipId.value) {
|
||||
// Clear hover if no item is selected in any context
|
||||
hoveringHistoryRowId.value = null
|
||||
}
|
||||
}, [
|
||||
globalKeyboardSelectedItemId.value,
|
||||
currentNavigationContext.value,
|
||||
keyboardSelectedClipId.value,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const listenToClipboardUnlisten = listen(
|
||||
@ -497,18 +773,6 @@ export default function ClipboardHistoryPage() {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCtrlReleased.value) {
|
||||
hoveringHistoryRowId.value = null
|
||||
keyboardSelectedItemId.value = null
|
||||
}
|
||||
}, [isCtrlReleased.value])
|
||||
|
||||
useEffect(() => {
|
||||
if (copiedItem || pastedItem) {
|
||||
isCtrlReleased.value = true
|
||||
keyboardSelectedItemId.value = null
|
||||
navigatedWithCtrl.value = false
|
||||
}
|
||||
if (copiedItem && selectedHistoryItems.includes(copiedItem)) {
|
||||
setSelectedHistoryItems(prev => prev.filter(item => item !== copiedItem))
|
||||
}
|
||||
@ -1769,7 +2033,12 @@ export default function ClipboardHistoryPage() {
|
||||
}
|
||||
isPasted={historyId === pastedItemValue}
|
||||
isKeyboardSelected={
|
||||
historyId === keyboardSelectedItemId.value
|
||||
(currentNavigationContext.value ===
|
||||
'history' ||
|
||||
currentNavigationContext.value ===
|
||||
null) &&
|
||||
historyId ===
|
||||
globalKeyboardSelectedItemId.value
|
||||
}
|
||||
isCopied={historyId === copiedItemValue}
|
||||
isSaved={historyId === savingItem}
|
||||
|
@ -98,6 +98,13 @@ export const creatingMenuItemCurrentMenuId = signal(false)
|
||||
|
||||
export const isNavBarHovering = signal(false)
|
||||
|
||||
// Keyboard Navigation Signals for Board/History Context
|
||||
export const currentNavigationContext = signal<'history' | 'board' | null>(null)
|
||||
export const currentBoardIndex = signal<number>(0)
|
||||
export const keyboardSelectedItemId = signal<UniqueIdentifier | null>(null)
|
||||
export const keyboardSelectedClipId = signal<UniqueIdentifier | null>(null)
|
||||
export const keyboardSelectedBoardId = signal<UniqueIdentifier | null>(null)
|
||||
|
||||
export function closeEdit() {
|
||||
showDeleteClipConfirmationId.value = null
|
||||
editBoardItemId.value = null
|
||||
|
Loading…
x
Reference in New Issue
Block a user